diff --git a/.cve-lite/baseline.json b/.cve-lite/baseline.json index 122897d3..7837628e 100644 --- a/.cve-lite/baseline.json +++ b/.cve-lite/baseline.json @@ -5,9 +5,7 @@ { "name": "js-yaml", "version": "3.14.2", - "advisoryIds": [ - "GHSA-h67p-54hq-rp68" - ] + "advisoryIds": ["GHSA-h67p-54hq-rp68"] } ] } diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2dbdcb7e..e8e2bcc5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Report a bug or incorrect behavior in CVE Lite CLI title: "[Bug] " labels: bug assignees: "" - --- ## Summary diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index cc8e376a..b259f11e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an improvement for CVE Lite CLI title: "[Feature] " labels: enhancement assignees: "" - --- ## Problem @@ -18,6 +17,7 @@ Describe the feature or improvement. ## Why it fits this project Explain why this aligns with CVE Lite CLI's goals: + - practical developer usability - clear remediation guidance - JS/TS dependency scanning diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 57881df1..1699b611 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,9 +8,9 @@ Explain the problem being solved. ## What changed -- -- -- +- +- +- ## Validation diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3f1b549..b4e6b0c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,6 @@ on: branches: [main] pull_request: - permissions: contents: read @@ -15,10 +14,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@v6 - name: Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@v6 with: node-version: 20 cache: npm @@ -26,6 +25,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Rebuild native dependencies + run: npm rebuild better-sqlite3 --ignore-scripts=false + - name: Test run: npm test diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ee5df304..ae0f63fa 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,15 +23,15 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@v6 - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v4 + uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/docs-site.yml b/.github/workflows/docs-site.yml index 7acaeffe..412fb124 100644 --- a/.github/workflows/docs-site.yml +++ b/.github/workflows/docs-site.yml @@ -30,10 +30,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@v6 - name: Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@v6 with: node-version: 22 cache: npm @@ -49,7 +49,7 @@ jobs: - name: Upload Pages artifact if: github.event_name == 'push' - uses: actions/upload-pages-artifact@v5 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 uses: actions/upload-pages-artifact@v5 with: path: website/build @@ -77,4 +77,4 @@ jobs: steps: - name: Deploy id: deployment - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 uses: actions/deploy-pages@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e0c0ac84..b61b0b1e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,12 +15,12 @@ jobs: steps: - name: Checkout at release tag - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} - name: Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@v6 with: node-version: 20 cache: npm @@ -28,6 +28,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Rebuild native dependencies + run: npm rebuild better-sqlite3 --ignore-scripts=false + - name: Test run: npm test @@ -38,9 +41,9 @@ jobs: run: npm pack - name: Attest build provenance - uses: actions/attest-build-provenance@v2 + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 uses: actions/attest-build-provenance@v2 with: - subject-path: 'cve-lite-cli-*.tgz' + subject-path: "cve-lite-cli-*.tgz" - name: Upload tarball to release env: diff --git a/.github/workflows/self-scan.yml b/.github/workflows/self-scan.yml index b2890560..15acebcb 100644 --- a/.github/workflows/self-scan.yml +++ b/.github/workflows/self-scan.yml @@ -11,10 +11,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@v6 - name: Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@v6 with: node-version: 20 cache: npm @@ -22,6 +22,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Rebuild native dependencies + run: npm rebuild better-sqlite3 --ignore-scripts=false + - name: Build run: npm run build @@ -35,17 +38,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@v6 - name: Run CVE Lite CLI GitHub Action against this repo - uses: OWASP/cve-lite-cli@v1 + uses: OWASP/cve-lite-cli@97546a6e88f381bc77233dda58c5fdef375c312d # v1.24.0 uses: OWASP/cve-lite-cli@v1 with: verbose: "true" fail-on: high sarif: "true" - name: Upload SARIF to GitHub Code Scanning - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 uses: github/codeql-action/upload-sarif@v4 if: always() with: sarif_file: ${{ github.workspace }} diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..954ec182 --- /dev/null +++ b/.npmrc @@ -0,0 +1,11 @@ +# npm security best practices +# Source: https://github.com/lirantal/npm-security-best-practices + +# SECURITY: do not run any lifecycle scripts (postinstall) etc +ignore-scripts=true + +# SECURITY: reject git-source dependencies (git+ssh:// etc) +allow-git=none + +# SECURITY: block packages newer than 5 days +min-release-age=5 diff --git a/.trunk/.gitignore b/.trunk/.gitignore new file mode 100644 index 00000000..15966d08 --- /dev/null +++ b/.trunk/.gitignore @@ -0,0 +1,9 @@ +*out +*logs +*actions +*notifications +*tools +plugins +user_trunk.yaml +user.yaml +tmp diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml new file mode 100644 index 00000000..b40ee9d7 --- /dev/null +++ b/.trunk/configs/.markdownlint.yaml @@ -0,0 +1,2 @@ +# Prettier friendly markdownlint config (all formatting rules disabled) +extends: markdownlint/style/prettier diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml new file mode 100644 index 00000000..184e251f --- /dev/null +++ b/.trunk/configs/.yamllint.yaml @@ -0,0 +1,7 @@ +rules: + quoted-strings: + required: only-when-needed + extra-allowed: ["{|}"] + key-duplicates: {} + octal-values: + forbid-implicit-octal: true diff --git a/.trunk/configs/svgo.config.mjs b/.trunk/configs/svgo.config.mjs new file mode 100644 index 00000000..55b4a7a1 --- /dev/null +++ b/.trunk/configs/svgo.config.mjs @@ -0,0 +1,14 @@ +export default { + plugins: [ + { + name: "preset-default", + params: { + overrides: { + removeViewBox: false, // https://github.com/svg/svgo/issues/1128 + sortAttrs: true, + removeOffCanvasPaths: true, + }, + }, + }, + ], +}; diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml new file mode 100644 index 00000000..f49916f1 --- /dev/null +++ b/.trunk/trunk.yaml @@ -0,0 +1,37 @@ +# This file controls the behavior of Trunk: https://docs.trunk.io/cli +# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml +version: 0.1 +cli: + version: 1.25.0 +# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) +plugins: + sources: + - id: trunk + ref: v1.10.2 + uri: https://github.com/trunk-io/plugins +# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes) +runtimes: + enabled: + - node@22.16.0 + - python@3.14.4 +# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration) +lint: + enabled: + - actionlint@1.7.12 + - checkov@3.3.1 + - git-diff-check + - grype@0.114.0 + - markdownlint@0.49.0 + - osv-scanner@2.4.0 + - oxipng@10.1.1 + - pinact@4.1.0 + - prettier@3.8.4 + - svgo@4.0.1 + - trufflehog@3.95.6 + - yamllint@1.38.0 +actions: + enabled: + - trunk-announce + - trunk-check-pre-push + - trunk-fmt-pre-commit + - trunk-upgrade-available diff --git a/CHANGELOG.md b/CHANGELOG.md index f5540ba6..a0ebb295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,68 +7,83 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.24.0] - 2026-06-17 ### Added + - `--sarif` can now be combined with `--report` to write both a SARIF file and an HTML report in one scan; useful for CI pipelines that upload to GitHub Code Scanning and also attach an HTML artifact for human review (#681) ### Fixed + - Fix commands now include `-D` flag for dev dependencies (`npm install -D`, `pnpm add -D`, `yarn add -D`, `bun add --dev`); mixed dev/prod batches split into separate commands (#689, #690) - GitHub Action now installs cve-lite-cli via `npm install --prefix` and appends the bin dir to `$GITHUB_PATH`, fixing `cve-lite: not found` errors on npm 10.x runners where npx cannot resolve a binary name different from the package name (#691, #692) ### Changed + - Upgrade jest to 30.4.1; add `.cve-lite/baseline.json` to suppress unfixable `js-yaml@3.14.2` transitive dev dep (GHSA-h67p-54hq-rp68) (#693, #694) ### Docs + - Socket CLI comparison expanded with structured sections and concrete examples (#655) ## [1.23.1] - 2026-06-15 ### Performance -- npm lockfile graph construction reduced from O(E*V) to O(E) using Set accumulators for edge lists (#652) + +- npm lockfile graph construction reduced from O(E\*V) to O(E) using Set accumulators for edge lists (#652) - npm lockfile graph nodes and arrays pre-frozen at construction time; redundant uniquePathArrays removed (#654) - Remediation package lookup replaced with Map for O(1) access (#653) ### Docs + - Four new case studies: Strapi (Yarn Berry, 2,887 packages), Twenty (Yarn Berry, 5,451 packages), Presenton (dual npm lockfiles), Payload CMS (pnpm, 2,602 packages) (#593, #594, #595, #638) - OWASP Lab Project status reflected across all project docs: README, CONTRIBUTING, comparison page, case studies index, and press page (#673) ### Changed + - SARIF, CycloneDX, and HTML reporter file-write cleanup refactored for clarity; test spy coverage refined (#637) - Case study contribution scope clarified in CONTRIBUTING: contributors submit case-study files only, shared index files maintained by maintainer (#649) ## [1.23.0] - 2026-06-13 ### Added + - Graded output for MAL- advisories from git sources: terminal shows `⚠ Git source (SHA-pinned)` or `⚠ Git source (floating ref)` with resolved URL; HTML report shows orange badge; `isGitSource()` and `hasCommitShaPinning()` detection functions (#618) - `multiple-versions-same-pkg` and `git-source-mal` example fixtures ### Fixed + - Error handling and cleanup for SARIF, CycloneDX, and HTML report file writes; pre-existing directories preserved on write failure (#628) - Duplicate `db.close()` call removed from osv-sync catch block that could mask original error (#629) ### Performance + - CVE detail fetches now run concurrently via `runWithConcurrency` instead of serially — 2.2x faster on cold cache for large lockfiles (#645) - Packument cache pre-warmed before transitive remediation loop to eliminate serial npm registry round-trips (#645) ## [1.22.0] - 2026-06-11 ### Added + - Dev dependency labelling: terminal output and HTML report now show `direct · dev` / `transitive · dev` for findings from devDependencies; Yarn Classic and Berry parsers updated to detect dev status (#578) - `yarn-within-range` and `dev-only-finding` example fixtures for regression testing (#537, #613) ### Fixed + - Private registry detection (`⚠ Unverifiable (private source)`) now works for pnpm (legacy and v9), Yarn Classic, and Bun lockfiles — previously only npm was supported (#616) ## [1.21.0] - 2026-06-09 ### Added + - Ratcheting mode: `--ratchet` saves current findings as `.cve-lite/baseline.json`; subsequent scans auto-suppress known findings and only report new ones above the baseline ### Docs + - Dedicated ratcheting mode page at `/docs/ratcheting` - MAL- advisory handling and unverifiable private source findings documented in how-remediation-works ## [1.20.0] - 2026-06-08 ### Added + - `--create-pr` flag: after `--fix`, commits lockfile changes and opens a GitHub PR via `gh` with a descriptive title listing the upgraded packages and vulnerability count (#518) - `--base ` flag to set the base branch for `--create-pr` (default: main) - `bun-within-range` fixture: Bun parser updated to reconstruct transitive paths from package relationships; within-range remediation now works for Bun lockfiles (#562) @@ -77,17 +92,20 @@ All notable changes to CVE Lite CLI will be documented in this file. - `mal-private-registry` example fixture demonstrating unverifiable MAL- output for private registry packages (#588) ### Fixed + - Yarn Classic parser now reconstructs full transitive dependency paths using BFS graph walk; within-range resolver now correctly suggests `yarn upgrade ` for deep chains (#576) - MAL- advisories for packages resolved from a private registry now surface as "Unverifiable (private source)" instead of a false-positive "Malicious" finding (#588) ## [1.19.2] - 2026-06-05 ### Fixed + - Transitive vulnerability findings now correctly classified as transitive when the same package is also installed as a direct dependency at a different version. Previously `uuid@8.3.2` (transitive) was classified as `direct` because `uuid@14.0.0` was in `package.json`, generating a wrong `npm install` command. - Skip reason version hint now uses the validated fix version consistently with the findings table, eliminating discrepancies between the two. - `--help` output no longer repeats the tool name and version already shown in the banner. ### Changed + - Skipped findings in verbose terminal output now show the advisory version with a gray `⊘` suffix instead of the full green version, signalling it is a hint only. A note below the table points to `--report` for detailed skip reasons. - HTML report findings table: `⊘ Skipped (N)` filter button added (only shown when there are skipped findings). Fixed column shows `⊘` icon with tooltip for skipped findings. - HTML report: findings section top margin fixed, scan notes moved to bottom after all important sections. @@ -95,12 +113,14 @@ All notable changes to CVE Lite CLI will be documented in this file. - Nested lockfile informational message moved from warnings (yellow) to notes (gray). ### Docs + - Added How Remediation Works page with Mermaid dependency tree diagrams and tabbed package manager commands. - Added usage examples to `--help` output. - 7 new case studies: Gatsby, Vercel AI SDK, Mastra, Lit, LangChain.js, OpenAI Agents JS, n8n. - Community contributors section added to README. ### Docs + - Gatsby case study added with verified baseline scan of a Yarn Classic lockfile snapshot (`examples/gatsby/`, 3,568 packages, 128 findings at revision `1f38c85`), including CVE Lite CLI vs `yarn audit` comparison. - Examples readme, docs sidebar, case studies index, and README updated to reference the Gatsby fixture and case study. - Vercel AI SDK case study added with verified baseline scan of a pnpm lockfile snapshot (`examples/vercel-ai-sdk/`, 3,570 packages, 55 findings at revision `3215032`), including CVE Lite CLI vs `pnpm audit` comparison. @@ -114,23 +134,28 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.19.1] - 2026-06-02 ### Fixed + - Within-range transitive fix now detected for dependency chains deeper than 2 levels: when the immediate parent's declared range already covers a safe version of the vulnerable package, CVE Lite now suggests a lockfile refresh (`npm update `) instead of an incorrect best-effort parent upgrade. Adds `examples/wrong-parent/` as a reproducible fixture for this class of bug. ## [1.19.0] - 2026-06-01 ### Added + - Multi-folder scan for monorepos without a root lockfile: when `cve-lite .` is run from a directory with no lockfile but two or more lockfiles in subfolders, the scanner automatically switches to multi-folder mode. Each subfolder is scanned independently, findings and fix commands are grouped per subfolder in terminal output, a single HTML report is generated with collapsible per-folder sections, and `--json` output includes a `subfolder` field on each finding. ### Fixed + - `isNewer` update check now correctly parses pre-release version strings (e.g. `1.19.0-alpha.1`) by stripping the pre-release suffix before comparison, preventing alpha users from seeing a false "downgrade available" prompt. ## [1.18.2] - 2026-06-01 ### Added + - `--debug` flag writes a timestamped JSONL log file alongside the scan with network requests, cache hits, and runtime events; a single stderr line identifies the log file path. - Unknown-severity findings no longer silently dropped from compact and verbose terminal output; compact mode now shows all direct unknown findings regardless of how many critical/high findings are present. ### Fixed + - pnpm v9 aliased dependencies (where the lockfile dep name differs from the real package name, e.g. `'@remix-run/dev': '@vercel/remix-run-dev@1.16.1'`) now resolve correctly through the transitive graph. Five downstream bugs fixed: wrong direct-install commands for unresolvable findings, missing parent upgrade suggestions for deep chains, blank context column for covered findings, and reason text being overwritten by lower-severity findings. - Spinner completion lines (`✓ Loaded package matches from cache`, etc.) no longer printed to stdout in `--json` mode. - Offline advisory database errors now include a sync hint (`cve-lite advisories sync`) to guide users to resolution. @@ -138,12 +163,15 @@ All notable changes to CVE Lite CLI will be documented in this file. - Case studies index page added to resolve a Docusaurus build break. ### Changed + - CI workflow now declares explicit `permissions: contents: read`, matching the least-privilege stance already in place on all other workflows. ### Tests + - Unit tests added for `src/cli/validate.ts` covering all flag-conflict validation branches. ### Docs + - Visual Studio Code case study with verified baseline scan of a root npm lockfile snapshot (`examples/vscode/`, 1,374 packages, 9 findings at revision `bc678ca`), including CVE Lite CLI vs `npm audit` comparison. - Storybook case study with verified baseline scan of a Yarn Berry monorepo lockfile snapshot (`examples/storybook/`, 3,008 packages, 92 findings at revision `cc19ae1`), including CVE Lite CLI vs `yarn npm audit` comparison. - Help Net Security monthly roundup (May 2026) added to press coverage. @@ -152,14 +180,17 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.18.1] - 2026-05-27 ### Added + - Corporate SSL proxy support: `--ca-cert ` flag passes a PEM CA certificate for a single scan or advisory sync; `cve-lite config set ca-cert ` saves the path persistently in `~/.cve-lite-cli/config.json` so every future invocation uses it automatically; `cve-lite config show` and `cve-lite config unset ca-cert` manage the saved value. Cert is validated as a readable PEM file before saving. GitHub Action gains a matching `ca-cert` input. - Workspace-scoped direct fix commands for monorepos: when scanning an npm, pnpm, yarn, or bun workspace project, direct dependency upgrade commands now include the appropriate workspace flag (`npm install -w `, `pnpm add --filter ./path`, `yarn workspace add`, `bun add --filter `) so the install targets the correct workspace scope rather than the project root. ### Changed + - Extracted all fix execution logic from `src/index.ts` into `src/utils/fix-runner.ts`: `applyFixesIfRequested`, `FixExecutionResult`, `printFixModeSummary` join the previously extracted `buildFixCommandParts`, `runInstallCommand`, and `commandLabelForPackageManager`. - Extracted `pluralize` utility to `src/utils/string.ts`, eliminating repeated count ternaries across 9 files. ### Docs + - New Corporate SSL Proxy guide covering one-time config setup, per-invocation flag, cert export from IT/keychain/browser, and air-gapped advisory sync fallback. - CLI reference updated with Network/SSL section and `config` subcommand docs. - Troubleshooting page updated with SSL certificate errors entry. @@ -173,13 +204,16 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.18.0] - 2026-05-25 ### Added + - Show targeted retry and offline hints for OSV 429 rate-limit and 5xx server error responses. - Emit lockfile-refresh fix commands for pnpm (`pnpm update`), yarn (`yarn upgrade`), and bun (`bun update`) when the parent's declared range already covers the fixed transitive dependency version. ### Fixed + - Added package manager hint to `--fix` command failure errors. ### Changed + - Workspace-scoped lockfile-refresh commands now generated for pnpm, yarn, and bun when the parent's declared range already covers the safe transitive version; lockfile-refresh targets appear in their own fix-plan sections rather than mixed with direct-fix targets; fix coverage count ("Running these commands should fix X of Y findings") added to both terminal and HTML output; "within current range" label renamed to "lockfile refresh" with context strings rewritten to plainly state the parent already permits the safe child version. - Unified excluded directory list for `--usage` source scanning with the shared `EXCLUDED_DIRS` constant. - Extracted `formatAdvisoryDbFreshness` and `relativeAge` from `src/index.ts` into `src/utils/time.ts`. @@ -191,29 +225,35 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.17.3] - 2026-05-22 ### Fixed + - SSL certificate errors from corporate proxy inspection now reliably show actionable `NODE_EXTRA_CA_CERTS` and `NODE_TLS_REJECT_UNAUTHORIZED=0` guidance by checking Node.js error codes and walking the error cause chain, rather than string matching on the top-level message. ## [1.17.2] - 2026-05-22 ### Fixed + - SSL certificate errors from corporate proxy inspection now show a clear, actionable message with `NODE_EXTRA_CA_CERTS` and `NODE_TLS_REJECT_UNAUTHORIZED=0` workarounds instead of a raw Node.js TLS error. ## [1.17.1] - 2026-05-22 ### Fixed + - Validated fix version now shown in the finding line and verbose table instead of the raw OSV hint, preventing confusing downgrade suggestions. - Malicious advisory findings (`MAL-*`) now surface a clear removal message across all output modes: inline hint in compact, `⚠ Malicious` badge and removal legend in verbose, and `⚠ Malicious` badge with tooltip in the HTML report. ## [1.17.0] - 2026-05-20 ### Added + - CVE count now shown alongside package count in all output modes: terminal summary reads `✗ Found 26 packages (35 CVEs)`, compact output reads `26 packages · 35 CVEs`, verbose quick-take reads `35 CVEs matched overall`, and the HTML report gains a dedicated CVEs severity card alongside the Packages card. - npm-shrinkwrap.json support: the scanner now detects and parses `npm-shrinkwrap.json` with correct precedence over `package-lock.json` when both are present. ### Fixed + - `security-events: write` permission added to the self-scan CI job so SARIF uploads succeed. ### Docs + - Getting Started page title shortened and added to top nav. - Ghost CMS case study added with full Before/After fix journey. - Socket CLI comparison expanded with structured sections. @@ -224,62 +264,75 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.16.0] - 2026-05-13 ### Added + - `--cdx` flag writes a CycloneDX 1.4 JSON SBOM (`cve-lite-scan-.cdx.json`) to the current directory. The SBOM includes all lockfile packages as components — not just vulnerable ones — making it suitable as a compliance artifact even on a clean scan. Vulnerability data is attached for any CVE findings, deduplicated by CVE ID with multiple `affects` references when the same CVE affects more than one package. Runnable fix commands are included as recommendations when available. - GitHub Action gains a `cdx` input (default `"false"`) to enable CycloneDX SBOM output from the Action. - Self-scan CI workflow now generates a SARIF file and uploads findings to GitHub Code Scanning via `github/codeql-action/upload-sarif`. ### Fixed + - `--sarif` and `--cdx` now suppress terminal table output, matching the behaviour of `--json`. Running any export flag shows only the spinner progress and the saved file path. Use `--verbose` alongside an export flag to restore full terminal output. ### Changed + - Output file writing (JSON, SARIF, CycloneDX) extracted from `index.ts` into a dedicated `write-outputs.ts` dispatcher module, keeping `index.ts` lean as new export formats are added. ## [1.15.1] - 2026-05-12 ### Added + - GitHub Action now exposes `--usage`, `--only-used`, `--sarif`, and `--no-cache` inputs. The `no-cache` input defaults to `true` in CI since runners are ephemeral. - `--sarif` flag writes a SARIF 2.1.0 file to the current directory for upload to GitHub Code Scanning. One result per CVE, rules deduplicated, severity mapped to SARIF levels. ## [1.15.0] - 2026-05-11 ### Added + - `--json` output is now saved to a timestamped file (`cve-lite-scan-YYYY-MM-DDTHH-MM-SS.json`) in the current directory, keeping stdout free for human-readable messages. The banner and spinner are no longer suppressed in `--json` mode. Advisory source and offline mode lines no longer appear in `--json` stdout. - New `install-skill` subcommand writes AI assistant skill files for Claude Code, Codex CLI, Gemini CLI, Cursor, and GitHub Copilot into the current project directory. Append-style files (`AGENTS.md`, `GEMINI.md`, `.github/copilot-instructions.md`) are created if missing, appended to if no CVE Lite section exists, or replaced in place if a section already exists — running the command twice is safe. Commit the generated files to share the context with your team. ### Fixed + - Transitive parent-upgrade guidance now marks commands as path-specific when they only cover a subset of a vulnerable package's dependency paths. Covered and remaining paths are exposed in JSON; terminal output and HTML report show the same partial-path note. - pnpm lockfile traversal now preserves multiple dependency paths for repeated package versions instead of stopping after the first matching key. Path count and depth caps bound the traversal to avoid runaway graph walks. ### Changed + - Dedicated caching guide added covering the 30-minute TTL, false negative risk window, and `--no-cache` flag behavior. ## [1.14.0] - 2026-05-06 ### Added + - `--no-cache` flag forces a fresh OSV query for all packages in a single scan, bypassing the `queryEntries` cache while still writing results back so subsequent runs benefit from caching as normal. Mutually exclusive with `--offline` and `--offline-db`. - Transitive context column added to the HTML report findings table, showing the dependency path from each vulnerable transitive package back to a direct dependency. - Transitive findings in terminal output now show a ⚠ no-fix indicator when no safe upgrade is available, distinguishing unfixable transitive issues from ones that can be resolved. ### Fixed + - `queryEntries` cache now expires after 30 minutes. Previously, a clean result (no vulnerabilities) was cached indefinitely, meaning a package that acquired a new CVE after the initial scan would be silently missed on all subsequent scans until the cache was manually deleted. All entries — both clean and non-empty — are now re-queried after 30 minutes. Existing v2 cache files are migrated automatically and treated as stale on first run. ### Changed + - OSV batch queries now run in parallel with a concurrency cap of 5, reducing cold scan time from ~14s to ~7.5s on large lockfiles (~1700 packages). - Cache file format bumped from v2 to v3. `queryEntries` values now store `{ vulnIds, cachedAt }` instead of a bare `string[]`. v2 files are migrated transparently on load. ## [1.13.0] - 2026-05-06 ### Added + - Yarn Berry (v2+) lockfile support. The parser now detects the `__metadata:` block and routes to a dedicated Berry parser that extracts packages from `resolution:` fields. Non-npm resolutions (workspace, patch, file) are skipped automatically. Yarn 1 behavior is unchanged. - Curated in-repo vulnerable example fixtures under `examples/` for contributor testing, covering direct-fixable, transitive-path-high, transitive-only, direct-and-transitive, npm workspace, yarn-berry, and a documentation-site project. A readme documents each fixture's purpose, package manager, and scan command. - New CLI Reference documentation page listing every flag with defaults, descriptions, examples, and mutual-exclusion notes. ### Fixed + - BFS path-expansion loop in npm lockfile graph traversal no longer hangs on lockfiles with cyclic or fan-in dependency graphs. Added `MAX_PATH_DEPTH = 10` to cap path length and replaced `O(n)` `queue.shift()` with an index-based `O(1)` dequeue, eliminating unbounded array allocation and GC pressure that caused 100% CPU hangs on moderately sized lockfiles. - npm transitive parent chain reconstruction now correctly resolves hoisted packages back to their logical parent using the lockfile dependency declarations. - Yarn Berry lockfiles no longer throw "Unknown token" on the `__metadata:` block. ### Changed + - Output summary now renders severity counts as a box-drawing table (`Critical`, `High`, `Medium`, `Low`, `Unknown`) instead of inline text, making severity distribution visible at a glance. - `--all` flag now appends the full findings table in compact (default) mode, not only in `--verbose` mode. The "Tip: use --all…" message is suppressed when `--all` is already active. - Coverage notes now appear after the findings table in verbose output. @@ -289,28 +342,33 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.12.1] - 2026-05-02 ### Added + - Release tarballs attached to each GitHub release are now cryptographically signed using GitHub's Sigstore-backed Artifact Attestations. The signing keys are ephemeral OIDC-issued keys generated per build, so no long-lived private signing key exists on either GitHub or the npm registry. Verification is documented in the README under "Security and verification" using `gh attestation verify cve-lite-cli-X.Y.Z.tgz --repo OWASP/cve-lite-cli`. - New `## Governance` section in the README documenting the project's governance model, key roles, decision-making process, and dispute-resolution path. - New `## Security and verification` section in the README explaining how to verify a downloaded release tarball and how to verify the npm-installed copy via `npm audit signatures`. - New `## Coding standards` section in the contributor guide describing the TypeScript style baseline, naming conventions, comment policy, and the categories of change that get pushed back during review. ### Changed + - The Code of Conduct has moved from `src/docs/CODE_OF_CONDUCT.md` to `CODE_OF_CONDUCT.md` at the repository root so GitHub auto-detects it on the Community Standards page. The CoC text itself is unchanged, and a link was added to the Community section of the README. - The contributor guide's testing expectations are now an explicit policy rather than a soft suggestion: any new feature, behavior change, or bug fix that affects scan logic, parsing, output, or remediation must be covered by automated unit tests in the same pull request, with practical exceptions called out for documentation-only and genuinely untestable changes. ## [1.12.0] - 2026-05-02 ### Added + - HTML report findings now show the actual fix command (e.g. `npm install @`) with a Copy button when one is available, instead of always showing a descriptive prose recommendation. Findings without a runnable command show the recommendation as plain text without a misleading Copy button. - Serialized findings now expose a `runnableFixCommand: string | null` field for programmatic consumers of the JSON output. - New "Offline vs Online Results" documentation page explaining the two advisory sources, what stays the same across modes, the intentional behavior differences (registry-validated fix versions, parent-version upgrades), and freshness considerations on both sides. ### Fixed + - Offline scans now produce a Suggested Fix Plan that matches online scans for direct upgrades and in-range parent updates. Previously the fix plan was empty in offline mode because the validation gate treated an unset `validatedFirstFixedVersion` as "validation failed" rather than "validation did not run". - Offline transitive remediation is now resolved against the lockfile graph, with safe-child candidates synthesized from the advisory's `firstFixedVersion` when the npm registry is not available. The "update parent within current range" path now works offline; the "upgrade parent to a newer version" path remains online-only because it requires the parent's published manifests. - Withdrawn OSV advisories are now skipped during local advisory database sync, mirroring OSV's `/v1/querybatch` behavior. Offline scans no longer surface findings from advisories that have been retracted. ### Changed + - The repository's user-facing documentation now lives exclusively under `website/docs`, which backs the published site at `https://owasp.org/cve-lite-cli/`. Documentation links in the README point at the published guides rather than at Markdown source files. The previous `/docs` directory has been removed. - GitHub Actions workflows updated to current versions. - Public site homepage layout polished for better readability across viewport sizes. @@ -318,11 +376,13 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.11.0] - 2026-04-30 ### Added + - npm transitive remediation now builds a logical dependency graph from `package-lock.json` so hoisted packages can be mapped back to their actual parent chain. - npm transitive findings can now recommend `npm update ` when a safe child version is reachable within the current parent dependency range. - The CLI now shows progress while analyzing vulnerability findings after advisory details are loaded, avoiding a silent pause during fix-target validation and transitive remediation analysis. ### Fixed + - npm workspace scans now preserve workspace-local package path context for dependency paths and remediation resolution. - npm transitive parent upgrade recommendations now respect parent dependency ranges before suggesting a target. - npm alias nodes in package locks now keep their alias identity when building the remediation graph. @@ -330,20 +390,24 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.10.0] - 2026-04-28 ### Added + - HTML report now includes breaking change indicators, validation statistics, scan notes, and a search/filter control in the findings table. ### Fixed + - Transitive vulnerability findings now display tier-aware, actionable guidance instead of the generic "Upgrade the parent dependency chain" message. When a primary parent package is identified, it is named explicitly. When no dependency path data is available, the output honestly says so and directs developers to inspect their lockfile. - Fix plan skip reasons now distinguish between findings where a parent is known but no safe upgrade version was identified (Tier 2) and findings with no dependency path data at all (Tier 3). - Urgent fix plan table now renders parent-upgrade targets in their own table with a Context column showing which vulnerable package each parent upgrade resolves. ### Changed + - CI integration docs updated to reference the `OWASP/cve-lite-cli` GitHub Action and include the `--all` flag in example commands. - Comparison docs expanded with a dedicated GitHub Dependabot section covering advisory database differences, methodology, and where CVE Lite CLI provides more actionable output. ## [1.9.0] - 2026-04-25 ### Added + - `--report [dir]` flag generates a self-contained HTML vulnerability dashboard written to a local directory (default: `./cve-report/`). The report opens automatically in the browser on completion. - `--no-open` flag suppresses the automatic browser launch when used with `--report`. - HTML report includes severity summary cards, an interactive findings table with filter controls, copy-ready fix commands, expandable dependency paths, and CVE/GHSA links to osv.dev and GitHub Security Advisories. @@ -353,6 +417,7 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.8.0] - 2026-04-21 ### Added + - Usage-aware dependency analysis phase 1: The CLI now statically analyzes project source code to detect if vulnerable dependencies are actually imported and reachable. - Added `--usage` and `--only-used` flags. `Used` findings bubble to the top, and `--only-used` aggressively filters out unreachable/unused dependencies to eliminate noise. - CLI tables now feature a dedicated `Usage` column indicating import counts or `unused` status, color-coded red and green. @@ -361,21 +426,25 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.7.1] - 2026-04-18 ### Fixed + - Pre-release versions (e.g. `-next.*`, `-beta.*`, `-alpha.*`, `-rc.*`) are now suppressed as fix targets across all three resolution paths: OSV advisory data, parent upgrade resolution, and direct fix validation. When the only available fixed version is a pre-release, the fix hint shows `—` and no fix command is generated. ## [1.7.0] - 2026-04-17 ### Added + - pnpm lockfile v9 support — the v9 format (default in current pnpm installations) uses `name@version` keys and a `snapshots` section instead of the legacy `/name/version` and `packages` layout; the parser now branches on `lockfileVersion` and routes v9+ lockfiles through a dedicated path, eliminating false negatives on modern pnpm projects - Analog case study — full scan-fix workflow on a real pnpm v9 Angular monorepo (3,367 packages), including a comparison table against `pnpm audit`, fix journey, and baseline findings table - Baseline findings tables backported to NestJS and Juice Shop case studies for structural consistency across all studies ### Fixed + - BFS path-tracking in the pnpm parser replaced path-fingerprint `seenPaths` with a visited-key `seenKeys` set, eliminating exponential queue growth through circular dependency chains in large lockfiles (e.g. Analog's 15 circular deps) ## [1.6.0] - 2026-04-16 ### Added + - `bun.lock` parser — resolves package names and versions from Bun's JSONC lockfile format (v1.1.38+), with dev-only detection via workspace dependency manifests and `--prod-only` support - `bun add` fix commands — fix command output now detects Bun projects and emits `bun add @` alongside the existing npm/pnpm/yarn equivalents - Breaking change labels — fix command tables now flag major-version upgrade targets (e.g. `8.5.1 → 9.0.0`) with a `(breaking change)` annotation so developers know before running the command @@ -383,19 +452,23 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.5.4] - 2026-04-16 ### Fixed + - OSV `MODERATE` severity label now correctly maps to `medium` — packages like `got` and `micromatch` were previously classified as `unknown` and excluded from the default medium+ findings table - Validation table (Package / Current / Recommended target / Versions scanned / Still known vulnerable) now renders for urgent (high/critical) direct fix sections; it was missing after packages were reclassified from low to high by the CVSS vector fix in v1.5.3 - Transitive findings without a parent upgrade path no longer appear in the no-auto-fix section; they are already covered by fix plan step 2, so the duplication was confusing ### Changed + - Renamed "Not included automatically" to "No auto-fix command available for these direct dependencies" to accurately describe what is shown ## [1.5.3] - 2026-04-16 ### Fixed + - CVSS vector strings (e.g. `CVSS:3.1/AV:N/...`) were misclassified as low severity because the version number in the prefix (`3.1`) was extracted by the score parser and treated as a base score. All CVSS_V3-backed advisories now fall through to `database_specific.severity` and report the correct label. Packages like `crypto-js` (critical) and `braces` (high) were previously silently under-reported. ### Changed + - condensed README and extracted detailed content into standalone docs: offline advisory DB guide, CI integration guide, architecture overview, comparison guide, roadmap, troubleshooting, and parser coverage matrix - docs site updated with SEO meta tags, Open Graph, Twitter Card, JSON-LD structured data, Free/Local/Fast hero pillars, badge section, and GitHub icon nav link - screenshots shown side-by-side with click-to-enlarge @@ -404,18 +477,21 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.5.2] - 2026-04-10 ### Added + - scoped `--fix` mode for validated direct dependency remediation with package-manager-native apply behavior - automatic rescan after successful `--fix` apply and concise fix summary output (applied fixes, skipped findings, remaining severity mix) - dedicated `--fix` documentation guide and refreshed website/README guidance - Juice Shop case-study evidence for `--fix` workflow output ### Changed + - CLI now includes explicit `--fix` help output and improved fix-phase progress messaging - README comparison table now includes an explicit auto-fix support column with caveated tool-by-tool notes ## [1.5.1] - 2026-04-10 ### Changed + - direct vs transitive relationship classification now treats only root manifest-declared dependencies as direct, reducing misleading root-level remediation commands in monorepo/tooling-heavy scans - verbose fix-command output now renders parent-upgrade sections in a structured table with package, current version, recommended target, and context columns - README, website copy, and NestJS case study wording now align with direct/transitive remediation actionability expectations and refreshed screenshot evidence @@ -423,12 +499,14 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.5.0] - 2026-04-09 ### Added + - lowest known non-vulnerable direct remediation target selection based on advisory-range validation across published versions - version-scan metrics for validated remediation targeting (scanned versions and still-vulnerable exclusions) - new automated tests for multi-step upgrade chains, overlapping advisories, and fallback behavior when advisory coverage is incomplete - richer NestJS case-study evidence with remediation table metrics and screenshot-backed command snapshots ### Changed + - direct remediation output now uses structured table rendering with package, current version, recommended target, scanned versions, and still known vulnerable columns - direct remediation tables now include a total row for consistent section-level summary in verbose output - compact output now includes validation-summary context when scanned-version metrics are available @@ -437,12 +515,14 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.4.0] - 2026-04-06 ### Added + - npm registry validation for direct fixed-version hints before surfacing copy-and-run commands - nearest-published fallback handling for unpublished npm fixed-version hints, with a dedicated registry-adjusted command section - explicit warning output for unpublishable fixed-version hints that cannot be turned into runnable commands - new NestJS case study documenting the local scan-fix-rescan workflow on a mainstream framework repository ### Changed + - Suggested fix commands now cover more than the urgent path, including additional direct fixes when confident targets are available - Verbose and compact output now highlight copy-and-run remediation commands more prominently and explain when the top-priority issue has no confident automatic command yet - README now positions the local remediation loop more clearly against slower pipeline-only scanning workflows @@ -451,11 +531,13 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.2.0] - 2026-04-04 ### Added + - Reusable first-party GitHub Action via `action.yml` for simple GitHub Actions adoption - Official workflow integration guidance for package scripts, opt-in `postinstall`, git hooks, CI, and scheduled advisory DB refreshes - Multi-column README table of contents for easier navigation ### Changed + - Simplified the reusable GitHub Action by removing built-in npm cache setup, improving reliability in external repositories - README now includes GitHub Action usage examples and clearer top-level navigation - Network and privacy documentation now reflects the current offline workflow and advisory DB operational model @@ -463,15 +545,18 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.1.1] - 2026-04-04 ### Added + - Advisory DB freshness reporting during offline scans, including warnings when the local DB appears stale or is missing sync metadata ### Changed + - Advisory sync ingestion is now significantly faster through bulk SQLite transactions and prepared statement reuse - README now documents the measured advisory sync benchmark and keeps the offline freshness guidance aligned with the shipped behavior ## [1.1.0] - 2026-04-04 ### Added + - Local SQLite advisory database foundation for offline advisory lookups - `cve-lite advisories sync` command to download the official OSV npm dump and build the local advisory DB - Offline scanning with `--offline` using the default local advisory DB @@ -479,6 +564,7 @@ All notable changes to CVE Lite CLI will be documented in this file. - Progress reporting during advisory DB sync, including download and ingest progress ### Changed + - CLI output now reports when offline mode is enabled and when the local advisory DB is being used as the advisory source - README now highlights offline advisory DB support, offline workflows, and scheduled DB refresh guidance more prominently - Coverage notes now clarify that offline scans do not make outbound advisory API calls @@ -486,17 +572,21 @@ All notable changes to CVE Lite CLI will be documented in this file. ## [1.0.6] - 2026-04-02 ### Added + - Best-effort parent upgrade guidance for transitive vulnerabilities - Verbose output now shows recommended parent upgrades when available while preserving full dependency paths ### Changed + - Compact output now surfaces more actionable remediation guidance for transitive issues - README updated to reflect the new remediation behavior ## [1.0.5] - 2026-04-01 ### Added + - Configurable OSV endpoint support ### Changed + - README updates and documentation fixes diff --git a/README.md b/README.md index 61b897b2..dc2d7cec 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - [![OWASP Lab Project](https://img.shields.io/badge/OWASP-Lab%20Project-48A646?logo=owasp)](https://owasp.org/cve-lite-cli) [![npm version](https://img.shields.io/npm/v/cve-lite-cli)](https://www.npmjs.com/package/cve-lite-cli) [![npm downloads](https://img.shields.io/npm/dm/cve-lite-cli)](https://www.npmjs.com/package/cve-lite-cli) @@ -15,11 +14,11 @@

CVE Lite CLI

- **🏆 Officially recognized as an [OWASP Lab Project](https://owasp.org/cve-lite-cli)** +**🏆 Officially recognized as an [OWASP Lab Project](https://owasp.org/cve-lite-cli)**

Vulnerability scanning that belongs in your terminal — not your CI pipeline.
Scan your lockfile, get copy-and-run fix commands, and ship clean code.

- Scan. Understand. Fix. +Scan. Understand. Fix.
@@ -179,29 +178,30 @@ For full CI patterns including offline workflows, git hooks, and scripted automa No other free tool combines all of the following: lockfile scanning across npm, pnpm, Yarn, and Bun; parent-aware transitive remediation that tells you which package to upgrade (not just which one is vulnerable); fix version validation before suggesting an upgrade; and a fully offline advisory DB for restricted environments. -| Capability | CVE Lite CLI | npm audit | OSV-Scanner | Snyk CLI | Socket CLI | -|---|:---:|:---:|:---:|:---:|:---:| -| JS/TS lockfile scanning | ✅ | ✅ | ✅ | ✅ | ✅ | -| npm + pnpm + Yarn + Bun support | ✅ | ❌ | ✅ | ✅ | ✅ | -| No account required | ✅ | ✅ | ✅ | ❌ | ❌ | -| Free to use | ✅ | ✅ | ✅ | ❌ | ❌ | -| Usage-aware reachability scanning | ✅ | ❌ | ❌ | ✅ | ⚠️ | -| Direct vs transitive visibility | ✅ | ⚠️ | ✅ | ✅ | ✅ | -| Dev vs runtime dependency labelling | ✅ | ⚠️ | ❌ | ⚠️ | ❌ | -| Copy-and-run fix commands | ✅ | ❌ | ❌ | ✅ | ⚠️ | -| Transitive parent update guidance | ✅ | ❌ | ⚠️ | ⚠️ | ⚠️ | -| Suggested remediation plan | ✅ | ❌ | ⚠️ | ✅ | ⚠️ | -| Ratcheting mode (baseline suppression) | ✅ | ❌ | ❌ | ⚠️ | ❌ | -| Interactive HTML report | ✅ | ❌ | ❌ | ✅ | ❌ | -| SARIF / GitHub Code Scanning output | ✅ | ❌ | ✅ | ✅ | ❌ | -| JSON output | ✅ | ✅ | ✅ | ✅ | ✅ | -| Offline/local advisory DB | ✅ | ❌ | ⚠️ | ❌ | ❌ | +| Capability | CVE Lite CLI | npm audit | OSV-Scanner | Snyk CLI | Socket CLI | +| -------------------------------------- | :----------: | :-------: | :---------: | :------: | :--------: | +| JS/TS lockfile scanning | ✅ | ✅ | ✅ | ✅ | ✅ | +| npm + pnpm + Yarn + Bun support | ✅ | ❌ | ✅ | ✅ | ✅ | +| No account required | ✅ | ✅ | ✅ | ❌ | ❌ | +| Free to use | ✅ | ✅ | ✅ | ❌ | ❌ | +| Usage-aware reachability scanning | ✅ | ❌ | ❌ | ✅ | ⚠️ | +| Direct vs transitive visibility | ✅ | ⚠️ | ✅ | ✅ | ✅ | +| Dev vs runtime dependency labelling | ✅ | ⚠️ | ❌ | ⚠️ | ❌ | +| Copy-and-run fix commands | ✅ | ❌ | ❌ | ✅ | ⚠️ | +| Transitive parent update guidance | ✅ | ❌ | ⚠️ | ⚠️ | ⚠️ | +| Suggested remediation plan | ✅ | ❌ | ⚠️ | ✅ | ⚠️ | +| Ratcheting mode (baseline suppression) | ✅ | ❌ | ❌ | ⚠️ | ❌ | +| Interactive HTML report | ✅ | ❌ | ❌ | ✅ | ❌ | +| SARIF / GitHub Code Scanning output | ✅ | ❌ | ✅ | ✅ | ❌ | +| JSON output | ✅ | ✅ | ✅ | ✅ | ✅ | +| Offline/local advisory DB | ✅ | ❌ | ⚠️ | ❌ | ❌ | ✅ = built-in strength · ⚠️ = partial or workflow-dependent · ❌ = not a core strength The transitive parent guidance is a key difference: CVE Lite CLI avoids recommending direct installs for packages that are only present transitively. For npm lockfiles, it can identify when `npm update ` is enough to re-resolve a known non-vulnerable child within the current parent range, and when the parent package itself needs an upgrade. ### About Socket CLI + Socket provides deep supply-chain analysis (malware, typosquatting, maintainer risk) but requires a paid account for full features. CVE Lite CLI remains one of the few fully free, offline, and account-free options with strong transitive analysis. For detailed per-tool analysis, see [Comparison with other tools](https://owasp.org/cve-lite-cli/docs/comparison). @@ -266,12 +266,12 @@ CVE Lite CLI is an [OWASP Lab Project](https://owasp.org/cve-lite-cli) — revie CVE Lite CLI fills a specific gap — fast, local-first JS/TS dependency scanning close to release time — that broader OWASP tools are not optimized for: -| Tool | Focus | -|---|---| -| CVE Lite CLI | Lockfile-first, local developer CLI, remediation-focused, JS/TS | -| OWASP Dependency-Check | Multi-language, SAST-style, broader ecosystem | -| OWASP dep-scan | Multi-language and environment, SBOM and cloud-native | -| OWASP Dependency-Track | Platform and SBOM management, not a local CLI | +| Tool | Focus | +| ---------------------- | --------------------------------------------------------------- | +| CVE Lite CLI | Lockfile-first, local developer CLI, remediation-focused, JS/TS | +| OWASP Dependency-Check | Multi-language, SAST-style, broader ecosystem | +| OWASP dep-scan | Multi-language and environment, SBOM and cloud-native | +| OWASP Dependency-Track | Platform and SBOM management, not a local CLI | CVE Lite CLI complements these tools. It is not a replacement for continuous monitoring or full SBOM management — it is the fast local check you run before pushing. @@ -359,6 +359,7 @@ CVE Lite CLI is designed to be fast. Scanning a lockfile is nearly instantaneous `--fix` applies validated direct dependency fixes using your project's package manager, then rescans automatically. In the current version it: + - applies only direct dependency fixes with a validated lowest known non-vulnerable target - uses `npm install`, `pnpm add`, `yarn add`, or `bun add` based on your lockfile - rescans automatically after applying fixes @@ -574,7 +575,7 @@ New here? See [CONTRIBUTING.md](https://github.com/OWASP/cve-lite-cli/blob/main/ --- -*Most tools tell you what's wrong. CVE Lite CLI tells you what to run.* +_Most tools tell you what's wrong. CVE Lite CLI tells you what to run._ ## License diff --git a/assets/OWASP_Logo_Black_TM.png b/assets/OWASP_Logo_Black_TM.png index b3550abf..ee3c635a 100644 Binary files a/assets/OWASP_Logo_Black_TM.png and b/assets/OWASP_Logo_Black_TM.png differ diff --git a/assets/all.png b/assets/all.png index c16b16e5..a2e7cf1b 100644 Binary files a/assets/all.png and b/assets/all.png differ diff --git a/assets/default-output.png b/assets/default-output.png index 2cba2d19..14e6f05b 100644 Binary files a/assets/default-output.png and b/assets/default-output.png differ diff --git a/assets/diagram.png b/assets/diagram.png index 6b3a6cf3..befbe4bb 100644 Binary files a/assets/diagram.png and b/assets/diagram.png differ diff --git a/assets/favicon.png b/assets/favicon.png index 62b0817d..b01e0e2c 100644 Binary files a/assets/favicon.png and b/assets/favicon.png differ diff --git a/assets/html-report-dashboard.png b/assets/html-report-dashboard.png index d59b5fff..9f8b23a7 100644 Binary files a/assets/html-report-dashboard.png and b/assets/html-report-dashboard.png differ diff --git a/assets/logo-with-title-removebg-preview.png b/assets/logo-with-title-removebg-preview.png index 651a94de..7512d021 100644 Binary files a/assets/logo-with-title-removebg-preview.png and b/assets/logo-with-title-removebg-preview.png differ diff --git a/assets/logo-with-title.png b/assets/logo-with-title.png index 419a63fe..b437720c 100644 Binary files a/assets/logo-with-title.png and b/assets/logo-with-title.png differ diff --git a/assets/logo.png b/assets/logo.png index 40546a60..7a782450 100644 Binary files a/assets/logo.png and b/assets/logo.png differ diff --git a/assets/logos-combined.svg b/assets/logos-combined.svg index 4851d4e2..653397f5 100644 --- a/assets/logos-combined.svg +++ b/assets/logos-combined.svg @@ -1,17 +1 @@ - - - - - - - - - + - - \ No newline at end of file ++ \ No newline at end of file diff --git a/assets/medium.png b/assets/medium.png index 14b3b601..68b166a7 100644 Binary files a/assets/medium.png and b/assets/medium.png differ diff --git a/assets/nest-case-study-1.png b/assets/nest-case-study-1.png index 32f2860a..cd9af018 100644 Binary files a/assets/nest-case-study-1.png and b/assets/nest-case-study-1.png differ diff --git a/assets/nest-case-study-2.png b/assets/nest-case-study-2.png index 2d9e461e..de00635e 100644 Binary files a/assets/nest-case-study-2.png and b/assets/nest-case-study-2.png differ diff --git a/assets/nest-case-study-3.png b/assets/nest-case-study-3.png index 488e76c0..28a0efe9 100644 Binary files a/assets/nest-case-study-3.png and b/assets/nest-case-study-3.png differ diff --git a/assets/nest-case-study-4.png b/assets/nest-case-study-4.png index 7ff9a489..bfb27497 100644 Binary files a/assets/nest-case-study-4.png and b/assets/nest-case-study-4.png differ diff --git a/assets/nestjs-logo.svg b/assets/nestjs-logo.svg index 196f6b71..08d96cfa 100644 --- a/assets/nestjs-logo.svg +++ b/assets/nestjs-logo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/owasp-juice-shop-1.png b/assets/owasp-juice-shop-1.png index c5b9b0e9..c60aec3e 100644 Binary files a/assets/owasp-juice-shop-1.png and b/assets/owasp-juice-shop-1.png differ diff --git a/assets/owasp-juice-shop-2.png b/assets/owasp-juice-shop-2.png index 2d15a716..21f6878f 100644 Binary files a/assets/owasp-juice-shop-2.png and b/assets/owasp-juice-shop-2.png differ diff --git a/assets/owasp-juice-shop-3.png b/assets/owasp-juice-shop-3.png index 31f29b98..f38ea6ec 100644 Binary files a/assets/owasp-juice-shop-3.png and b/assets/owasp-juice-shop-3.png differ diff --git a/assets/owasp-juice-shop-4.png b/assets/owasp-juice-shop-4.png index 8088fc08..8e7e4b60 100644 Binary files a/assets/owasp-juice-shop-4.png and b/assets/owasp-juice-shop-4.png differ diff --git a/assets/owasp-juice-shop-5.png b/assets/owasp-juice-shop-5.png index a278643b..95f3a917 100644 Binary files a/assets/owasp-juice-shop-5.png and b/assets/owasp-juice-shop-5.png differ diff --git a/assets/owasp-juice-shop-auto-fix.png b/assets/owasp-juice-shop-auto-fix.png index 03d724f5..37d77b69 100644 Binary files a/assets/owasp-juice-shop-auto-fix.png and b/assets/owasp-juice-shop-auto-fix.png differ diff --git a/assets/small.png b/assets/small.png index 3b86a452..a2417b71 100644 Binary files a/assets/small.png and b/assets/small.png differ diff --git a/assets/verbose-output-1.png b/assets/verbose-output-1.png index 62b91ae1..7f2b315f 100644 Binary files a/assets/verbose-output-1.png and b/assets/verbose-output-1.png differ diff --git a/assets/verbose-output-2.png b/assets/verbose-output-2.png index d17b12d5..4781e37d 100644 Binary files a/assets/verbose-output-2.png and b/assets/verbose-output-2.png differ diff --git a/assets/verbose-output-3.png b/assets/verbose-output-3.png index f0f9fff3..15a03333 100644 Binary files a/assets/verbose-output-3.png and b/assets/verbose-output-3.png differ diff --git a/docs/output-before-after.md b/docs/output-before-after.md index 3433ff96..a50242b0 100644 --- a/docs/output-before-after.md +++ b/docs/output-before-after.md @@ -171,6 +171,7 @@ Dependency paths to inspect ``` **Removed:** + - "🚀 Top priority fixes" — repeated findings already visible in the fix-commands table and the findings table - "📋 Suggested fix plan" — same guidance a third time, grouped by severity - "Where the issues are" — direct/transitive counts already shown in "Quick take" above and in the table "Type" column diff --git a/examples/direct-and-transitive/package.json b/examples/direct-and-transitive/package.json index 0744869a..4d51e481 100644 --- a/examples/direct-and-transitive/package.json +++ b/examples/direct-and-transitive/package.json @@ -17,4 +17,4 @@ "overrides": { "path-to-regexp": "1.7.0" } -} \ No newline at end of file +} diff --git a/examples/direct-fixable/package.json b/examples/direct-fixable/package.json index e0ed6d3a..a1a933a8 100644 --- a/examples/direct-fixable/package.json +++ b/examples/direct-fixable/package.json @@ -13,4 +13,4 @@ "dependencies": { "axios": "0.21.1" } -} \ No newline at end of file +} diff --git a/examples/lima-site/package.json b/examples/lima-site/package.json index 86d96f38..78b09dbb 100644 --- a/examples/lima-site/package.json +++ b/examples/lima-site/package.json @@ -19,4 +19,4 @@ "hugo-extended": "0.133.1", "postcss-cli": "^11.0.1" } -} \ No newline at end of file +} diff --git a/examples/pnpm-workspace/pnpm-workspace.yaml b/examples/pnpm-workspace/pnpm-workspace.yaml index 18ec407e..dee51e92 100644 --- a/examples/pnpm-workspace/pnpm-workspace.yaml +++ b/examples/pnpm-workspace/pnpm-workspace.yaml @@ -1,2 +1,2 @@ packages: - - 'packages/*' + - "packages/*" diff --git a/examples/readme.md b/examples/readme.md index 3bc2bbd0..2df2d2b2 100644 --- a/examples/readme.md +++ b/examples/readme.md @@ -8,156 +8,156 @@ Do not use these projects as application starter templates. Small curated projects committed to the repository. Clone the repo and scan immediately. -| Folder | Package Manager | Purpose | -|---|---|---| -| `direct-fixable` | npm | Direct vulnerability with a clear upgrade command available. | -| `transitive-path-high` | npm | High-severity transitive dependency path detection. | -| `transitive-only` | npm | Transitive-only vulnerabilities — no directly vulnerable deps. | -| `direct-and-transitive` | npm | Mixed direct and transitive vulnerability output. | -| `deep-chain-no-fix` | npm | 3-level chain where the intermediate parent range does not cover the fix — expects a parent upgrade, not `npm update`. | -| `down-grade` | npm | Advisory where the raw OSV hint is lower than the installed version. | -| `workspace` | npm (workspace) | npm workspace hoisting and multi-package scanning. | -| `yarn-berry` | Yarn Berry (v2+) | Yarn Berry lockfile format parsing (`__metadata:` block). | -| `yarn-classic` | Yarn Classic (v1) | Yarn v1 lockfile format with direct and transitive vulnerabilities. | -| `yarn-within-range` | Yarn Classic (v1) | Deep transitive chain where the parent's range already covers the fix; suggests `yarn upgrade `. | -| `bun-simple` | Bun | Minimal Bun lockfile with a direct and transitive vulnerability. | -| `bun-within-range` | Bun | Transitive follow-redirects fix within axios range — suggests `bun update follow-redirects`. | -| `bun-workspace` | Bun (workspace) | Bun workspace monorepo with workspace-scoped fix commands. | -| `pnpm-simple` | pnpm | Minimal pnpm v9 lockfile with a single direct vulnerability. | -| `pnpm-within-range` | pnpm | Transitive `qs` via `body-parser` where the parent range already covers the fix — expects `pnpm update qs`, not a parent bump. | -| `pnpm-aliased-chain` | pnpm | Deep transitive chain through a pnpm v9 aliased intermediate — path resolution must use the real package name. | -| `pnpm-workspace` | pnpm (workspace) | pnpm workspace monorepo with workspace-scoped fix commands. | -| `wrong-parent` | npm | 3-level transitive chain where the immediate parent's range already covers the fix — expects `npm update js-cookie`, not a parent bump. | -| `no-findings` | npm | Clean project with no known vulnerabilities — demonstrates success output. | -| `dev-only-finding` | npm | Vulnerable package that only appears in devDependencies — classified as a direct finding in full scans and excluded by `--prod-only`. | -| `any fixture` + `.cve-lite/baseline.json` | any | Run `cve-lite . --ratchet` on any fixture to establish a baseline. Rescan without the flag to see only new findings. `.cve-lite/` directories should NOT be committed from example fixtures. | -| `mal-private-registry` | npm | `node-ipc@9.2.3` with `resolved` pointing to a private registry — demonstrates `Unverifiable (private source)` output for MAL- advisories where the artifact origin cannot be confirmed. | -| `pnpm-mal-private-registry` | pnpm v9 | `node-ipc@9.2.3` resolved from a private registry — demonstrates `Unverifiable (private source)` detection for pnpm v9 lockfiles. | -| `pnpm-legacy-mal-private-registry` | pnpm legacy (v6) | `node-ipc@9.2.3` resolved from a private registry — demonstrates `Unverifiable (private source)` detection for pnpm v6/v7/v8 lockfiles. | -| `yarn-classic-mal-private-registry` | Yarn Classic (v1) | `node-ipc@9.2.3` resolved from a private registry — demonstrates `Unverifiable (private source)` detection for Yarn Classic lockfiles. | -| `bun-mal-private-registry` | Bun | `node-ipc@9.2.3` resolved from a private registry — demonstrates `Unverifiable (private source)` detection for Bun lockfiles. | -| `git-source-mal` | npm | `node-ipc@9.2.3` resolved from a git source URL pinned to a commit SHA — demonstrates `Git source (SHA-pinned)` badge for MAL- advisories where the package originates from a git repository rather than the npm registry. | -| `lima-site` | npm | Dev-dependency scanning in a documentation site. | +| Folder | Package Manager | Purpose | +| ----------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `direct-fixable` | npm | Direct vulnerability with a clear upgrade command available. | +| `transitive-path-high` | npm | High-severity transitive dependency path detection. | +| `transitive-only` | npm | Transitive-only vulnerabilities — no directly vulnerable deps. | +| `direct-and-transitive` | npm | Mixed direct and transitive vulnerability output. | +| `deep-chain-no-fix` | npm | 3-level chain where the intermediate parent range does not cover the fix — expects a parent upgrade, not `npm update`. | +| `down-grade` | npm | Advisory where the raw OSV hint is lower than the installed version. | +| `workspace` | npm (workspace) | npm workspace hoisting and multi-package scanning. | +| `yarn-berry` | Yarn Berry (v2+) | Yarn Berry lockfile format parsing (`__metadata:` block). | +| `yarn-classic` | Yarn Classic (v1) | Yarn v1 lockfile format with direct and transitive vulnerabilities. | +| `yarn-within-range` | Yarn Classic (v1) | Deep transitive chain where the parent's range already covers the fix; suggests `yarn upgrade `. | +| `bun-simple` | Bun | Minimal Bun lockfile with a direct and transitive vulnerability. | +| `bun-within-range` | Bun | Transitive follow-redirects fix within axios range — suggests `bun update follow-redirects`. | +| `bun-workspace` | Bun (workspace) | Bun workspace monorepo with workspace-scoped fix commands. | +| `pnpm-simple` | pnpm | Minimal pnpm v9 lockfile with a single direct vulnerability. | +| `pnpm-within-range` | pnpm | Transitive `qs` via `body-parser` where the parent range already covers the fix — expects `pnpm update qs`, not a parent bump. | +| `pnpm-aliased-chain` | pnpm | Deep transitive chain through a pnpm v9 aliased intermediate — path resolution must use the real package name. | +| `pnpm-workspace` | pnpm (workspace) | pnpm workspace monorepo with workspace-scoped fix commands. | +| `wrong-parent` | npm | 3-level transitive chain where the immediate parent's range already covers the fix — expects `npm update js-cookie`, not a parent bump. | +| `no-findings` | npm | Clean project with no known vulnerabilities — demonstrates success output. | +| `dev-only-finding` | npm | Vulnerable package that only appears in devDependencies — classified as a direct finding in full scans and excluded by `--prod-only`. | +| `any fixture` + `.cve-lite/baseline.json` | any | Run `cve-lite . --ratchet` on any fixture to establish a baseline. Rescan without the flag to see only new findings. `.cve-lite/` directories should NOT be committed from example fixtures. | +| `mal-private-registry` | npm | `node-ipc@9.2.3` with `resolved` pointing to a private registry — demonstrates `Unverifiable (private source)` output for MAL- advisories where the artifact origin cannot be confirmed. | +| `pnpm-mal-private-registry` | pnpm v9 | `node-ipc@9.2.3` resolved from a private registry — demonstrates `Unverifiable (private source)` detection for pnpm v9 lockfiles. | +| `pnpm-legacy-mal-private-registry` | pnpm legacy (v6) | `node-ipc@9.2.3` resolved from a private registry — demonstrates `Unverifiable (private source)` detection for pnpm v6/v7/v8 lockfiles. | +| `yarn-classic-mal-private-registry` | Yarn Classic (v1) | `node-ipc@9.2.3` resolved from a private registry — demonstrates `Unverifiable (private source)` detection for Yarn Classic lockfiles. | +| `bun-mal-private-registry` | Bun | `node-ipc@9.2.3` resolved from a private registry — demonstrates `Unverifiable (private source)` detection for Bun lockfiles. | +| `git-source-mal` | npm | `node-ipc@9.2.3` resolved from a git source URL pinned to a commit SHA — demonstrates `Git source (SHA-pinned)` badge for MAL- advisories where the package originates from a git repository rather than the npm registry. | +| `lima-site` | npm | Dev-dependency scanning in a documentation site. | ## In-repo snapshot: Astro Lockfile-only snapshot from [withastro/astro](https://github.com/withastro/astro) at revision `221bb4b36831f3fc278f05dc40a7498abb864ddf`. Commits `package.json` and `pnpm-lock.yaml` only — no application source. [Case study](../website/docs/case-studies/astro.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `astro` | pnpm | https://github.com/withastro/astro | Modern content/meta-framework monorepo — 2,228 packages, 34 findings. | +| Folder | Package Manager | Source | Purpose | +| ------- | --------------- | ---------------------------------- | --------------------------------------------------------------------- | +| `astro` | pnpm | https://github.com/withastro/astro | Modern content/meta-framework monorepo — 2,228 packages, 34 findings. | ## In-repo snapshot: Turborepo Lockfile-only snapshot from [vercel/turborepo](https://github.com/vercel/turborepo) at revision `c85d4104bdc18df051334210d29c49353c46facf`. Commits `package.json` and `pnpm-lock.yaml` only — no application source. [Case study](../website/docs/case-studies/turborepo.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `turborepo` | pnpm | https://github.com/vercel/turborepo | Monorepo build-system toolchain — 1,776 packages, 13 findings. | +| Folder | Package Manager | Source | Purpose | +| ----------- | --------------- | ----------------------------------- | -------------------------------------------------------------- | +| `turborepo` | pnpm | https://github.com/vercel/turborepo | Monorepo build-system toolchain — 1,776 packages, 13 findings. | ## In-repo snapshot: Visual Studio Code Lockfile-only snapshot from [microsoft/vscode](https://github.com/microsoft/vscode) at revision `bc678cad02f18de3e2b6bf72a8259e9fb322cdfc`. Commits root `package.json` and `package-lock.json` only — no application source. Scan scope is the root lockfile, not nested folders under `build/` or `extensions/`. [Case study](../website/docs/case-studies/vscode.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `vscode` | npm | https://github.com/microsoft/vscode | Developer-tool root lockfile — 1,374 packages, 9 findings (2 direct). | +| Folder | Package Manager | Source | Purpose | +| -------- | --------------- | ----------------------------------- | --------------------------------------------------------------------- | +| `vscode` | npm | https://github.com/microsoft/vscode | Developer-tool root lockfile — 1,374 packages, 9 findings (2 direct). | ## In-repo snapshot: Gatsby Lockfile-only snapshot from [gatsbyjs/gatsby](https://github.com/gatsbyjs/gatsby) at revision `1f38c85963fd6bcfa9ccee2f925e5e02b00eafbb`. Commits `package.json` and `yarn.lock` only — no application source. [Case study](../website/docs/case-studies/gatsby.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `gatsby` | Yarn Classic | https://github.com/gatsbyjs/gatsby | Large Yarn v1 monorepo — 3,568 packages, 128 findings (5 direct). | +| Folder | Package Manager | Source | Purpose | +| -------- | --------------- | ---------------------------------- | ----------------------------------------------------------------- | +| `gatsby` | Yarn Classic | https://github.com/gatsbyjs/gatsby | Large Yarn v1 monorepo — 3,568 packages, 128 findings (5 direct). | ## In-repo snapshot: Vercel AI SDK Lockfile-only snapshot from [vercel/ai](https://github.com/vercel/ai) at revision `3215032043569f75a97fadf2b08aa38f11b011af`. Commits `package.json` and `pnpm-lock.yaml` only — no application source. Distinct from the [Turborepo](https://github.com/vercel/turborepo) snapshot already in this repo. [Case study](../website/docs/case-studies/vercel-ai-sdk.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `vercel-ai-sdk` | pnpm | https://github.com/vercel/ai | AI SDK monorepo — 3,570 packages, 55 findings (3 direct). | +| Folder | Package Manager | Source | Purpose | +| --------------- | --------------- | ---------------------------- | --------------------------------------------------------- | +| `vercel-ai-sdk` | pnpm | https://github.com/vercel/ai | AI SDK monorepo — 3,570 packages, 55 findings (3 direct). | ## In-repo snapshot: Mastra Lockfile-only snapshot from [mastra-ai/mastra](https://github.com/mastra-ai/mastra) at revision `e9d54b281667477dd97b9dfc166b338f6d097fe8`. Commits `package.json` and `pnpm-lock.yaml` only — no application source. Largest in-repo fixture by resolved package count. [Case study](../website/docs/case-studies/mastra.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `mastra` | pnpm | https://github.com/mastra-ai/mastra | AI agent framework monorepo — 4,555 packages, 64 findings (4 direct). | +| Folder | Package Manager | Source | Purpose | +| -------- | --------------- | ----------------------------------- | --------------------------------------------------------------------- | +| `mastra` | pnpm | https://github.com/mastra-ai/mastra | AI agent framework monorepo — 4,555 packages, 64 findings (4 direct). | ## In-repo snapshot: Lit Lockfile-only snapshot from [lit/lit](https://github.com/lit/lit) at revision `20afabd3c5bfd49fdcdf1b8518e05c7f99a46db6`. Commits `package.json` and `package-lock.json` only — no application source. [Case study](../website/docs/case-studies/lit.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `lit` | npm (workspaces) | https://github.com/lit/lit | Web components monorepo — 2,059 packages, 99 findings (3 direct rollup). | +| Folder | Package Manager | Source | Purpose | +| ------ | ---------------- | -------------------------- | ------------------------------------------------------------------------ | +| `lit` | npm (workspaces) | https://github.com/lit/lit | Web components monorepo — 2,059 packages, 99 findings (3 direct rollup). | ## In-repo snapshot: LangChain.js Lockfile-only snapshot from [langchain-ai/langchainjs](https://github.com/langchain-ai/langchainjs) at revision `1503c9beaa6a578f6a30739b2cfc1af9d18dd805`. Commits `package.json` and `pnpm-lock.yaml` only — no application source. [Case study](../website/docs/case-studies/langchainjs.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `langchainjs` | pnpm | https://github.com/langchain-ai/langchainjs | LLM application framework monorepo — 2,174 packages, 13 findings (lean graph, 3 high). | +| Folder | Package Manager | Source | Purpose | +| ------------- | --------------- | ------------------------------------------- | -------------------------------------------------------------------------------------- | +| `langchainjs` | pnpm | https://github.com/langchain-ai/langchainjs | LLM application framework monorepo — 2,174 packages, 13 findings (lean graph, 3 high). | ## In-repo snapshot: OpenAI Agents SDK (JavaScript) Lockfile-only snapshot from [openai/openai-agents-js](https://github.com/openai/openai-agents-js) at revision `f76fc19fba03dfbecf34ffd92302543b3b1d4890`. Commits `package.json` and `pnpm-lock.yaml` only — no application source. [Case study](../website/docs/case-studies/openai-agents-js.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `openai-agents-js` | pnpm | https://github.com/openai/openai-agents-js | OpenAI Agents SDK monorepo — 1,683 packages, 31 findings (0 direct, transitive parent tracing). | +| Folder | Package Manager | Source | Purpose | +| ------------------ | --------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| `openai-agents-js` | pnpm | https://github.com/openai/openai-agents-js | OpenAI Agents SDK monorepo — 1,683 packages, 31 findings (0 direct, transitive parent tracing). | ## In-repo snapshot: n8n Lockfile-only snapshot from [n8n-io/n8n](https://github.com/n8n-io/n8n) at revision `e2e03948562e1c744be4ef7898b3b754fbdb6cf9`. Commits `package.json` and `pnpm-lock.yaml` only — no application source. [Case study](../website/docs/case-studies/n8n.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `n8n` | pnpm | https://github.com/n8n-io/n8n | Workflow automation monorepo — 3,746 packages, 32 findings (1 direct turbo, 31 transitive). | +| Folder | Package Manager | Source | Purpose | +| ------ | --------------- | ----------------------------- | ------------------------------------------------------------------------------------------- | +| `n8n` | pnpm | https://github.com/n8n-io/n8n | Workflow automation monorepo — 3,746 packages, 32 findings (1 direct turbo, 31 transitive). | ## In-repo snapshot: CamoFox Browser Lockfile-only snapshot from [jo-inc/camofox-browser](https://github.com/jo-inc/camofox-browser) at revision `ce3a3b085aacba73eb8de6c51733c19fb13bfae4`. Commits `package.json` and `package-lock.json` only — no application source. [Case study](../website/docs/case-studies/camofox-browser.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `camofox-browser` | npm | https://github.com/jo-inc/camofox-browser | AI agent browser automation — 435 packages, 2 findings (dual `qs` within-range + parent-upgrade fixes). | +| Folder | Package Manager | Source | Purpose | +| ----------------- | --------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| `camofox-browser` | npm | https://github.com/jo-inc/camofox-browser | AI agent browser automation — 435 packages, 2 findings (dual `qs` within-range + parent-upgrade fixes). | ## In-repo snapshot: Storybook Lockfile-only snapshot from [storybookjs/storybook](https://github.com/storybookjs/storybook) at revision `cc19ae1a2145e8f7cda8dc869f1b90d5346dcedb`. Commits `package.json` and `yarn.lock` only — no application source. [Case study](../website/docs/case-studies/storybook.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `storybook` | Yarn Berry | https://github.com/storybookjs/storybook | Cross-framework UI tooling monorepo — 3,008 packages, 92 findings. | +| Folder | Package Manager | Source | Purpose | +| ----------- | --------------- | ---------------------------------------- | ------------------------------------------------------------------ | +| `storybook` | Yarn Berry | https://github.com/storybookjs/storybook | Cross-framework UI tooling monorepo — 3,008 packages, 92 findings. | ## In-repo snapshot: Twenty Lockfile-only snapshot from [twentyhq/twenty](https://github.com/twentyhq/twenty) at revision `fc90b4ba8bb0a5d7c12c846fe9b2305527a0f7a8`. Commits `package.json` and `yarn.lock` only — no application source. [Case study](../website/docs/case-studies/twenty.md). -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `twenty` | Yarn Berry | https://github.com/twentyhq/twenty | Open-source CRM Nx monorepo — 5,451 packages, 105 findings (0 direct). | +| Folder | Package Manager | Source | Purpose | +| -------- | --------------- | ---------------------------------- | ---------------------------------------------------------------------- | +| `twenty` | Yarn Berry | https://github.com/twentyhq/twenty | Open-source CRM Nx monorepo — 5,451 packages, 105 findings (0 direct). | ## Local-only examples Full project clones used for real-world testing. Not committed to this repo — clone each separately into `examples/` for local use. -| Folder | Package Manager | Source | Purpose | -|---|---|---|---| -| `analog` | pnpm | https://github.com/analogjs/analog | pnpm lockfile parsing across a real-world Angular monorepo. | -| `nest` | npm | https://github.com/nestjs/nest | Real-world npm monorepo with transitive vulnerability chains. | -| `lint-staged` | npm | https://github.com/lint-staged/lint-staged | Real-world npm project for transitive CVE detection. | -| `juice-shop` | npm | https://github.com/juice-shop/juice-shop | Large real-world project (OWASP Juice Shop) with broad vulnerability surface. | -| `ghost` | pnpm | https://github.com/TryGhost/Ghost | Professional publishing platform — 26 transitive vulnerabilities in 4,447 packages including critical XSS in sanitize-html. | -| `prisma` | pnpm | https://github.com/prisma/prisma | TypeScript ORM — real-world pnpm monorepo scan. | -| `strapi` | Yarn Berry | https://github.com/strapi/strapi | Headless CMS monorepo — 2,887 packages, 2 direct findings (`lodash`, `qs`). | -| `payload` | pnpm | https://github.com/payloadcms/payload | TypeScript-first headless CMS — 2,602 packages, 1 direct finding, workspace-scoped remediation. | -| `presenton` | npm (dual) | https://github.com/presenton/presenton | AI presentation generator — dual lockfiles (root + Electron), 9 findings. | +| Folder | Package Manager | Source | Purpose | +| ------------- | --------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | +| `analog` | pnpm | https://github.com/analogjs/analog | pnpm lockfile parsing across a real-world Angular monorepo. | +| `nest` | npm | https://github.com/nestjs/nest | Real-world npm monorepo with transitive vulnerability chains. | +| `lint-staged` | npm | https://github.com/lint-staged/lint-staged | Real-world npm project for transitive CVE detection. | +| `juice-shop` | npm | https://github.com/juice-shop/juice-shop | Large real-world project (OWASP Juice Shop) with broad vulnerability surface. | +| `ghost` | pnpm | https://github.com/TryGhost/Ghost | Professional publishing platform — 26 transitive vulnerabilities in 4,447 packages including critical XSS in sanitize-html. | +| `prisma` | pnpm | https://github.com/prisma/prisma | TypeScript ORM — real-world pnpm monorepo scan. | +| `strapi` | Yarn Berry | https://github.com/strapi/strapi | Headless CMS monorepo — 2,887 packages, 2 direct findings (`lodash`, `qs`). | +| `payload` | pnpm | https://github.com/payloadcms/payload | TypeScript-first headless CMS — 2,602 packages, 1 direct finding, workspace-scoped remediation. | +| `presenton` | npm (dual) | https://github.com/presenton/presenton | AI presentation generator — dual lockfiles (root + Electron), 9 findings. | ## Usage diff --git a/examples/shrinkwrap/package.json b/examples/shrinkwrap/package.json index e0ed6d3a..a1a933a8 100644 --- a/examples/shrinkwrap/package.json +++ b/examples/shrinkwrap/package.json @@ -13,4 +13,4 @@ "dependencies": { "axios": "0.21.1" } -} \ No newline at end of file +} diff --git a/examples/transitive-only/package.json b/examples/transitive-only/package.json index deacac0e..d940c486 100644 --- a/examples/transitive-only/package.json +++ b/examples/transitive-only/package.json @@ -13,4 +13,4 @@ "dependencies": { "lint-staged": "15.2.0" } -} \ No newline at end of file +} diff --git a/examples/transitive-path-high/package.json b/examples/transitive-path-high/package.json index b71397f7..a7906c1f 100644 --- a/examples/transitive-path-high/package.json +++ b/examples/transitive-path-high/package.json @@ -16,4 +16,4 @@ "overrides": { "path-to-regexp": "1.7.0" } -} \ No newline at end of file +} diff --git a/examples/workspace/client/angular.json b/examples/workspace/client/angular.json index c0d91791..cbbc1cea 100644 --- a/examples/workspace/client/angular.json +++ b/examples/workspace/client/angular.json @@ -19,9 +19,7 @@ "outputPath": "dist/client", "index": "src/index.html", "browser": "src/main.ts", - "polyfills": [ - "zone.js" - ], + "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", "assets": [ { @@ -29,9 +27,7 @@ "input": "public" } ], - "styles": [ - "src/styles.css" - ], + "styles": ["src/styles.css"], "scripts": [] }, "configurations": { @@ -76,10 +72,7 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { - "polyfills": [ - "zone.js", - "zone.js/testing" - ], + "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "assets": [ { @@ -87,9 +80,7 @@ "input": "public" } ], - "styles": [ - "src/styles.css" - ], + "styles": ["src/styles.css"], "scripts": [] } } diff --git a/examples/workspace/client/src/app/app.component.html b/examples/workspace/client/src/app/app.component.html index 36093e18..5a108b4e 100644 --- a/examples/workspace/client/src/app/app.component.html +++ b/examples/workspace/client/src/app/app.component.html @@ -36,8 +36,17 @@ --pill-accent: var(--bright-blue); - font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + font-family: + "Inter", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Helvetica, + Arial, + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol"; box-sizing: border-box; -webkit-font-smoothing: antialiased; @@ -51,8 +60,17 @@ line-height: 100%; letter-spacing: -0.125rem; margin: 0; - font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + font-family: + "Inter Tight", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Helvetica, + Arial, + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol"; } @@ -231,19 +249,26 @@

Hello, {{ title }}

- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - + @for ( + item of [ + { title: "Explore the Docs", link: "https://angular.dev" }, + { + title: "Learn with Tutorials", + link: "https://angular.dev/tutorials", + }, + { title: "CLI Docs", link: "https://angular.dev/tools/cli" }, + { + title: "Angular Language Service", + link: "https://angular.dev/tools/language-service", + }, + { + title: "Angular DevTools", + link: "https://angular.dev/tools/devtools", + }, + ]; + track item.title + ) { + {{ item.title }} Hello, {{ title }} - diff --git a/examples/workspace/client/src/app/app.component.spec.ts b/examples/workspace/client/src/app/app.component.spec.ts index 36f4316c..3f40b37c 100644 --- a/examples/workspace/client/src/app/app.component.spec.ts +++ b/examples/workspace/client/src/app/app.component.spec.ts @@ -24,6 +24,8 @@ describe('AppComponent', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, client'); + expect(compiled.querySelector('h1')?.textContent).toContain( + 'Hello, client', + ); }); }); diff --git a/examples/workspace/client/src/app/app.component.ts b/examples/workspace/client/src/app/app.component.ts index dd6af98c..dac372aa 100644 --- a/examples/workspace/client/src/app/app.component.ts +++ b/examples/workspace/client/src/app/app.component.ts @@ -5,7 +5,7 @@ import { RouterOutlet } from '@angular/router'; selector: 'app-root', imports: [RouterOutlet], templateUrl: './app.component.html', - styleUrl: './app.component.css' + styleUrl: './app.component.css', }) export class AppComponent { title = 'client'; diff --git a/examples/workspace/client/src/app/app.config.ts b/examples/workspace/client/src/app/app.config.ts index a1e7d6f8..fb93f472 100644 --- a/examples/workspace/client/src/app/app.config.ts +++ b/examples/workspace/client/src/app/app.config.ts @@ -4,5 +4,8 @@ import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + ], }; diff --git a/examples/workspace/client/src/index.html b/examples/workspace/client/src/index.html index cef98023..c151b2c3 100644 --- a/examples/workspace/client/src/index.html +++ b/examples/workspace/client/src/index.html @@ -1,13 +1,13 @@ - - - Client - - - - - - - + + + Client + + + + + + + diff --git a/examples/workspace/client/src/main.ts b/examples/workspace/client/src/main.ts index 35b00f34..8882c451 100644 --- a/examples/workspace/client/src/main.ts +++ b/examples/workspace/client/src/main.ts @@ -2,5 +2,6 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; import { AppComponent } from './app/app.component'; -bootstrapApplication(AppComponent, appConfig) - .catch((err) => console.error(err)); +bootstrapApplication(AppComponent, appConfig).catch((err) => + console.error(err), +); diff --git a/examples/workspace/client/tsconfig.app.json b/examples/workspace/client/tsconfig.app.json index 3775b37e..8886e903 100644 --- a/examples/workspace/client/tsconfig.app.json +++ b/examples/workspace/client/tsconfig.app.json @@ -6,10 +6,6 @@ "outDir": "./out-tsc/app", "types": [] }, - "files": [ - "src/main.ts" - ], - "include": [ - "src/**/*.d.ts" - ] + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] } diff --git a/examples/workspace/client/tsconfig.spec.json b/examples/workspace/client/tsconfig.spec.json index 5fb748d9..e00e30e6 100644 --- a/examples/workspace/client/tsconfig.spec.json +++ b/examples/workspace/client/tsconfig.spec.json @@ -4,12 +4,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/spec", - "types": [ - "jasmine" - ] + "types": ["jasmine"] }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] } diff --git a/jest.config.mjs b/jest.config.mjs index 06dbd185..e529f901 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -11,8 +11,5 @@ export default { moduleNameMapper: { "^(\\.{1,2}/.*)\\.js$": "$1", }, - collectCoverageFrom: [ - "src/**/*.ts", - "!src/**/*.d.ts", - ], + collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"], }; diff --git a/package-lock.json b/package-lock.json index eb1b67a4..692275b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "1.24.0", "license": "MIT", "dependencies": { - "better-sqlite3": "^12.8.0", - "fflate": "^0.8.2", - "yaml": "^2.7.1", + "better-sqlite3": "12.10.1", + "fflate": "^0.8.3", + "yaml": "^2.9.0", "yarn-lockfile": "^1.1.0" }, "bin": { @@ -21,13 +21,14 @@ "@types/better-sqlite3": "^7.6.13", "@types/jest": "^30.0.0", "@types/node": "^24.0.0", - "jest": "^30.4.1", - "ts-jest": "^29.4.9", + "jest": "^29.7.0", + "lockfile-lint": "^4.7.4", + "ts-jest": "^29.1.2", "tsx": "^4.22.0", "typescript": "^5.8.2" }, "engines": { - "node": ">=18" + "node": ">=24" } }, "node_modules/@babel/code-frame": { @@ -288,13 +289,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -526,40 +527,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -1002,24 +969,6 @@ "node": ">=18" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1037,96 +986,139 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" + "engines": { + "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/console": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", - "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", + "node_modules/@jest/console/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "slash": "^3.0.0" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/core": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.1.tgz", - "integrity": "sha512-zNfBGtukVyc0ClmSzXgeP6eseumdekHfrqa++GsPK8ZUm9Hm3TY8X8LQvGfZVrp23RSz9ebbcruXnAv3no0Q+g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/pattern": "30.4.0", - "@jest/reporters": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.4.1", - "jest-config": "30.4.1", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-resolve-dependencies": "30.4.1", - "jest-runner": "30.4.1", - "jest-runtime": "30.4.1", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "jest-watcher": "30.4.1", - "pretty-format": "30.4.1", - "slash": "^3.0.0" + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1137,866 +1129,978 @@ } } }, - "node_modules/@jest/diff-sequences": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", - "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/environment": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", - "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", + "node_modules/@jest/core/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-mock": "30.4.1" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", + "node_modules/@jest/core/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.4.1", - "jest-snapshot": "30.4.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/expect-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", - "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", - "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", + "node_modules/@jest/diff-sequences": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@sinonjs/fake-timers": "^15.4.0", - "@types/node": "*", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" - }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", - "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", + "node_modules/@jest/environment/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/types": "30.4.1", - "jest-mock": "30.4.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/pattern": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", - "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "node_modules/@jest/environment/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-regex-util": "30.4.0" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", - "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/schemas": { + "node_modules/@jest/expect-utils": { "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "@jest/get-type": "30.1.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/snapshot-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", - "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", + "node_modules/@jest/expect/node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" + "jest-get-type": "^29.6.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "node_modules/@jest/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/expect/node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-result": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", - "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", + "node_modules/@jest/expect/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/types": "30.4.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-sequencer": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", - "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", + "node_modules/@jest/expect/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.4.1", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "slash": "^3.0.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", - "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", + "node_modules/@jest/expect/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", - "pirates": "^4.0.7", + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" + "stack-utils": "^2.0.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/types": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", - "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "node_modules/@jest/expect/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.4.0", - "@jest/schemas": "30.4.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", + "@jest/types": "^29.6.3", "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@jest/expect/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", + "node_modules/@jest/fake-timers/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@jest/fake-timers/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "node_modules/@jest/fake-timers/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@jest/fake-timers/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@pkgr/core": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", - "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "node_modules/@jest/fake-timers/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.0.0" + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, - "funding": { - "url": "https://opencollective.com/pkgr" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "type-detect": "4.0.8" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", - "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "node_modules/@jest/globals/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "node_modules/@jest/globals/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "node_modules/@jest/pattern/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/better-sqlite3": { - "version": "7.6.13", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", - "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "node_modules/@jest/reporters/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "node_modules/@jest/reporters/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "node_modules/@jest/reporters/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/node": { - "version": "24.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "node_modules/@jest/transform/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", - "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", - "cpu": [ - "arm" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", - "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", - "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", - "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", - "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", - "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", - "cpu": [ - "arm" - ], + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", - "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", - "cpu": [ - "arm" - ], + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 8" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", - "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", - "cpu": [ - "arm64" - ], + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", - "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", - "cpu": [ - "arm64" - ], + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", - "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", - "cpu": [ - "loong64" - ], + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } }, - "node_modules/@unrs/resolver-binding-linux-loong64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", - "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", - "cpu": [ - "loong64" - ], + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", - "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", - "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/types": "^7.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", - "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", - "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", - "cpu": [ - "s390x" - ], + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/types": "^7.28.2" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", - "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", - "cpu": [ - "x64" - ], + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/node": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", - "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", - "cpu": [ - "x64" - ], + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/node": "*" + } }, - "node_modules/@unrs/resolver-binding-openharmony-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", - "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", - "cpu": [ - "arm64" - ], + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", - "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", - "cpu": [ - "wasm32" - ], + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": ">=14.0.0" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", - "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", - "cpu": [ - "arm64" - ], + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@types/istanbul-lib-report": "*" + } }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", - "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", - "cpu": [ - "ia32" - ], + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", - "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", - "cpu": [ - "x64" - ], + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "license": "BSD-2-Clause" + "dependencies": { + "undici-types": "~7.18.0" + } }, - "node_modules/ansi-escapes": { + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause" + }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.3.tgz", + "integrity": "sha512-mQZgUSgFurUtA07ceMjxrWkYz8QtDuYkvPlu0ZqncgjopQ0t6CNEo/OSealkmnagSUx8ZD5ewvezUwUuMqutQg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", @@ -2013,16 +2117,13 @@ } }, "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=8" } }, "node_modules/ansi-styles": { @@ -2055,72 +2156,83 @@ "node": ">= 8" } }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "license": "Python-2.0" }, "node_modules/babel-jest": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", - "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.4.1", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.4.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" + "@babel/core": "^7.8.0" } }, "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=12" + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", - "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "license": "MIT", "dependencies": { - "@types/babel__core": "^7.20.5" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -2151,20 +2263,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", - "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.4.0", - "babel-preset-current-node-syntax": "^1.2.0" + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + "@babel/core": "^7.0.0" } }, "node_modules/balanced-match": { @@ -2195,9 +2307,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.37", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", - "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2208,9 +2320,9 @@ } }, "node_modules/better-sqlite3": { - "version": "12.8.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.8.0.tgz", - "integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==", + "version": "12.10.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.1.tgz", + "integrity": "sha512-HfFtzCqnSfwB3+HroF6PSKzyh+7RfNMGPCzHFUZXRlvrPCb4P3cvxKZNN43Sr7IrkofqQZM+gIvffGpA8VvqgA==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -2218,7 +2330,7 @@ "prebuild-install": "^7.1.1" }, "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x" + "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" } }, "node_modules/bindings": { @@ -2242,13 +2354,27 @@ } }, "node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/browserslist": { @@ -2328,1454 +2454,2276 @@ ], "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", + "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", "license": "MIT" }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" + "license": "ISC" }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, "engines": { - "node": ">=12" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=8.0.0" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "license": "MIT" }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ansi-regex": "^5.0.1" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" }, - "engines": { - "node": ">=10" + "bin": { + "handlebars": "bin/handlebars" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">= 8" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=4" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, "license": "MIT", "dependencies": { - "mimic-response": "^3.1.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dedent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", "engines": { - "node": ">=4.0.0" + "node": ">=0.8.19" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.5.375", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz", - "integrity": "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=0.12.0" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "dev": true, - "license": "MIT", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "Apache-2.0", + "license": "BSD-3-Clause", "dependencies": { - "bser": "2.1.1" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/jest-changed-files/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" + "node_modules/jest-circus/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "node_modules/jest-circus/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/jest-circus/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/jest-circus/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, "engines": { - "node": ">=6.9.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "node_modules/jest-cli/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=8.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/jest-config/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-diff": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.4.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.4.1" + }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, "engines": { - "node": ">=10.17.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jest-each/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=0.8.19" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/jest-environment-node/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/jest-environment-node/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/jest-haste-map/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "node_modules/jest-matcher-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/jest-message-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.4.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/jest-message-util/node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "@sinclair/typebox": "^0.34.0" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest": { + "node_modules/jest-message-util/node_modules/@jest/types": { "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.1.tgz", - "integrity": "sha512-ZXSQlP2bAgIq0XmJ49HNmrgXSoWoHEzciSw3YhPbOA3gVMl3CyLdHjbpV+dbR7ggOVwSEo4cl5OOaYwRrmWqEA==", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.4.1", - "@jest/types": "30.4.1", - "import-local": "^3.2.0", - "jest-cli": "30.4.1" - }, - "bin": { - "jest": "bin/jest.js" + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-message-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-changed-files": { + "node_modules/jest-mock": { "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", - "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.4.1", - "p-limit": "^3.1.0" + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-circus": { + "node_modules/jest-mock/node_modules/@jest/schemas": { "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.1.tgz", - "integrity": "sha512-kcCeuPX8Kh6TSujMOIzaAXWvvr41LFlbhLyEYzcc8doXIuGdX+hOxSxbAH7sJItvi1H2ZOU5B3ujD3FLiX5e4g==", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-runtime": "30.4.1", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "p-limit": "^3.1.0", - "pretty-format": "30.4.1", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "@sinclair/typebox": "^0.34.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-cli": { + "node_modules/jest-mock/node_modules/@jest/types": { "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.1.tgz", - "integrity": "sha512-wh86tmU2ak4aqaVg4Y+OwNys9Plrh4478+o8Zapeo8iz95uwW/WY+A4Yb3pd6C3ilHhY+Ue6V+yDM8G+zUDX+A==", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "jest-resolve": "*" }, "peerDependenciesMeta": { - "node-notifier": { + "jest-resolve": { "optional": true } } }, - "node_modules/jest-config": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.1.tgz", - "integrity": "sha512-AHAI8llsQFfz3oE6/AcBrP7tJdVnqczmBvnXONO8RWRqKefLaxKmkIUq0otc+QTZ/0gxBP7wBLfh6caMmNZcLA==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.4.0", - "@jest/test-sequencer": "30.4.1", - "@jest/types": "30.4.1", - "babel-jest": "30.4.1", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-circus": "30.4.1", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-runner": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "parse-json": "^5.2.0", - "pretty-format": "30.4.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, - "peerDependencies": { + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-diff": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", - "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "node_modules/jest-runner/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.4.0", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.4.1" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-docblock": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", - "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", + "node_modules/jest-runner/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.1.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", - "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", + "node_modules/jest-runner/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", - "chalk": "^4.1.2", - "jest-util": "30.4.1", - "pretty-format": "30.4.1" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", - "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1" + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-haste-map": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", - "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", - "picomatch": "^4.0.3", - "walker": "^1.0.8" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-leak-detector": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", - "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", + "node_modules/jest-runtime/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.4.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", - "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "node_modules/jest-runtime/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.4.1", - "pretty-format": "30.4.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", - "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "node_modules/jest-runtime/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.4.1", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-util": "30.4.1", - "picomatch": "^4.0.3", - "pretty-format": "30.4.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", - "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-util": "30.4.1" + "jest-get-type": "^29.6.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" + "node": ">=10" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-regex-util": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", - "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "node_modules/jest-snapshot/node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", - "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve-dependencies": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.1.tgz", - "integrity": "sha512-MstV4pRIfUBs9kuMHSzYHPMPYHqQGoknfRv6tEEpOX7755aaK4Hk5ICwTtOHyjCc3+hYMoxnKF3ENu3iayEIog==", + "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.4.0", - "jest-snapshot": "30.4.1" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.1.tgz", - "integrity": "sha512-NbStoXGdqMuYF8m7NEQ6FH2gH4eTvcSyz7BINLLuGacdAKtlsDa7PzPSZUtyiBfdMycO2Yeyn3ibfzUl2plRjA==", + "node_modules/jest-snapshot/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/environment": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-haste-map": "30.4.1", - "jest-leak-detector": "30.4.1", - "jest-message-util": "30.4.1", - "jest-resolve": "30.4.1", - "jest-runtime": "30.4.1", - "jest-util": "30.4.1", - "jest-watcher": "30.4.1", - "jest-worker": "30.4.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.1.tgz", - "integrity": "sha512-Ityu3lzs8+it7ABsi7N52Px3Ic1az9w+sBkP/r1xK5MaIq1BdYkYonftpwtX5AtibDSFrKTNEW9KLUXAynXIcA==", + "node_modules/jest-snapshot/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/globals": "30.4.1", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/types": "^29.6.3", "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", - "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.4.1", - "graceful-fs": "^4.2.11", - "jest-diff": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "pretty-format": "30.4.1", - "semver": "^7.7.2", - "synckit": "^0.11.8" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-snapshot/node_modules/semver": { @@ -3809,22 +4757,103 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate": { + "node_modules/jest-util/node_modules/@jest/schemas": { "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", - "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/types": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-util/node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "30.4.1" + "pretty-format": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -3840,41 +4869,91 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-watcher": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", - "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "30.4.1", - "string-length": "^4.0.2" + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", - "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.4.1", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" + "supports-color": "^8.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -3900,6 +4979,29 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -3933,6 +5035,16 @@ "node": ">=6" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -3963,6 +5075,41 @@ "node": ">=8" } }, + "node_modules/lockfile-lint": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/lockfile-lint/-/lockfile-lint-4.14.1.tgz", + "integrity": "sha512-NW0Tk1qfldhbhJWQENYQWANdmlanXKxvTJYRYKn56INYjaP2M07Ua2SJYkUMS+ZbYwxDzul/C6pDsV/NEXrl+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "cosmiconfig": "^9.0.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "lockfile-lint-api": "^5.9.2", + "yargs": "^17.7.2" + }, + "bin": { + "lockfile-lint": "bin/lockfile-lint.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/lockfile-lint-api": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/lockfile-lint-api/-/lockfile-lint-api-5.9.2.tgz", + "integrity": "sha512-3QhxWxl3jT9GcMxuCnTsU8Tz5U6U1lKBlKBu2zOYOz/x3ONUoojEtky3uzoaaDgExcLqIX0Aqv2I7TZXE383CQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@yarnpkg/parsers": "^3.0.0-rc.48.1", + "debug": "^4.3.4", + "object-hash": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -4033,6 +5180,30 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -4056,19 +5227,16 @@ } }, "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minimist": { @@ -4080,16 +5248,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -4109,22 +5267,6 @@ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4140,9 +5282,9 @@ "license": "MIT" }, "node_modules/node-abi": { - "version": "3.89.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", - "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -4152,9 +5294,9 @@ } }, "node_modules/node-abi/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4171,9 +5313,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", "dev": true, "license": "MIT", "engines": { @@ -4203,6 +5345,16 @@ "node": ">=8" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4283,12 +5435,18 @@ "node": ">=6" } }, - "node_modules/package-json-from-dist": { + "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "BlueOak-1.0.0" + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } }, "node_modules/parse-json": { "version": "5.2.0", @@ -4339,29 +5497,12 @@ "node": ">=8" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, - "license": "ISC" + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", @@ -4371,13 +5512,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -4449,6 +5590,26 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/pretty-format/node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -4462,6 +5623,20 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -4473,9 +5648,9 @@ } }, "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -4489,6 +5664,27 @@ ], "license": "MIT" }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -4513,6 +5709,13 @@ "node": ">=0.10.0" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/react-is-18": { "name": "react-is", "version": "18.3.1", @@ -4553,6 +5756,28 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -4576,6 +5801,51 @@ "node": ">=8" } }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4630,17 +5900,11 @@ } }, "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "ISC" }, "node_modules/simple-concat": { "version": "1.0.1", @@ -4687,6 +5951,13 @@ "simple-concat": "^1.0.0" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4718,13 +5989,6 @@ "source-map": "^0.6.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -4761,49 +6025,7 @@ "node": ">=10" } }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -4818,54 +6040,7 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -4878,16 +6053,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -4934,20 +6099,17 @@ "node": ">=8" } }, - "node_modules/synckit": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", - "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.3.6" - }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/synckit" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/tar-fs": { @@ -4993,63 +6155,30 @@ "node": ">=8" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "BSD-3-Clause" }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "is-number": "^7.0.0" }, "engines": { - "node": "*" + "node": ">=8.0" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/ts-jest": { - "version": "29.4.9", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", - "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", + "version": "29.4.11", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", + "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", "dev": true, "license": "MIT", "dependencies": { @@ -5059,7 +6188,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.4", + "semver": "^7.8.0", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -5100,9 +6229,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "dev": true, "license": "ISC", "bin": { @@ -5130,13 +6259,12 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/tsx": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.0.tgz", - "integrity": "sha512-8ccZMPD69s1AbKXx0C5ddTNZfNjwV04iIKgjZmKfKxMynEtSYcK0Lh7iQFh53fI5Yu4pb9usgAiqyPmEONaALg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", "dependencies": { @@ -5216,50 +6344,12 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, - "node_modules/unrs-resolver": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", - "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.4" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.12.2", - "@unrs/resolver-binding-android-arm64": "1.12.2", - "@unrs/resolver-binding-darwin-arm64": "1.12.2", - "@unrs/resolver-binding-darwin-x64": "1.12.2", - "@unrs/resolver-binding-freebsd-x64": "1.12.2", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", - "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", - "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", - "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", - "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-x64-musl": "1.12.2", - "@unrs/resolver-binding-openharmony-arm64": "1.12.2", - "@unrs/resolver-binding-wasm32-wasi": "1.12.2", - "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", - "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", - "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -5346,25 +6436,6 @@ "license": "MIT" }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -5382,64 +6453,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5447,17 +6460,17 @@ "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" + "signal-exit": "^3.0.7" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/y18n": { @@ -5478,9 +6491,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -5493,9 +6506,9 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -5521,51 +6534,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yarn-lockfile": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/yarn-lockfile/-/yarn-lockfile-1.1.1.tgz", diff --git a/package.json b/package.json index 368f36cb..349db754 100644 --- a/package.json +++ b/package.json @@ -11,20 +11,25 @@ "dev": "tsx src/index.ts .", "start": "node dist/index.js .", "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --passWithNoTests", - "test:watch": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watch --passWithNoTests" + "test:watch": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watch --passWithNoTests", + "lint:lockfile": "lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https" }, "dependencies": { - "better-sqlite3": "^12.8.0", - "fflate": "^0.8.2", - "yaml": "^2.7.1", + "better-sqlite3": "12.10.1", + "fflate": "^0.8.3", + "yaml": "^2.9.0", "yarn-lockfile": "^1.1.0" }, + "overrides": { + "js-yaml": "^4.2.0" + }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", "@types/jest": "^30.0.0", "@types/node": "^24.0.0", - "jest": "^30.4.1", - "ts-jest": "^29.4.9", + "jest": "^29.7.0", + "lockfile-lint": "^4.7.4", + "ts-jest": "^29.1.2", "tsx": "^4.22.0", "typescript": "^5.8.2" }, @@ -42,7 +47,7 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=24" }, "repository": { "type": "git", diff --git a/src/advisory/advisory-source.ts b/src/advisory/advisory-source.ts index 73a69cb2..242ba439 100644 --- a/src/advisory/advisory-source.ts +++ b/src/advisory/advisory-source.ts @@ -7,6 +7,9 @@ export interface AdvisoryResult { } export interface AdvisorySource { - queryBatch(packages: PackageRef[], meta?: { batchId?: string }): Promise; + queryBatch( + packages: PackageRef[], + meta?: { batchId?: string }, + ): Promise; getVuln(id: string): Promise; -} \ No newline at end of file +} diff --git a/src/advisory/local-advisory-source.ts b/src/advisory/local-advisory-source.ts index b9cf549a..6c356f7b 100644 --- a/src/advisory/local-advisory-source.ts +++ b/src/advisory/local-advisory-source.ts @@ -5,11 +5,16 @@ import { LocalAdvisoryDatabase } from "./local-db.js"; export class LocalAdvisorySource implements AdvisorySource { constructor(private readonly db: LocalAdvisoryDatabase) {} - queryBatch(packages: PackageRef[], _meta?: { batchId?: string }): Promise { - const results = packages.map(pkg => ({ + queryBatch( + packages: PackageRef[], + _meta?: { batchId?: string }, + ): Promise { + const results = packages.map((pkg) => ({ package: pkg.name, version: pkg.version, - vulnerabilities: this.db.findMatchingVulnerabilityIds(pkg).map(id => ({ id })), + vulnerabilities: this.db + .findMatchingVulnerabilityIds(pkg) + .map((id) => ({ id })), })); return Promise.resolve(results); @@ -18,7 +23,9 @@ export class LocalAdvisorySource implements AdvisorySource { getVuln(id: string): Promise { const vuln = this.db.getVulnerability(id); if (!vuln) { - return Promise.reject(new Error(`Local advisory database lookup failed for ${id}`)); + return Promise.reject( + new Error(`Local advisory database lookup failed for ${id}`), + ); } return Promise.resolve(vuln); diff --git a/src/advisory/local-db.ts b/src/advisory/local-db.ts index aa5c8788..f2bde33e 100644 --- a/src/advisory/local-db.ts +++ b/src/advisory/local-db.ts @@ -68,7 +68,10 @@ export class LocalAdvisoryDatabase { fs.mkdirSync(path.dirname(dbPath), { recursive: true }); } - this.db = new Database(dbPath, readonly ? { readonly: true, fileMustExist: true } : undefined); + this.db = new Database( + dbPath, + readonly ? { readonly: true, fileMustExist: true } : undefined, + ); this.db.pragma("foreign_keys = ON"); this.db.exec(SCHEMA_SQL); this.upsertAdvisoryStatement = this.db.prepare(` @@ -137,7 +140,9 @@ export class LocalAdvisoryDatabase { } getMetadata(): AdvisoryDbMetadata { - const row = this.getMetadataStatement.get() as { last_sync_at: string | null; source_url: string | null } | undefined; + const row = this.getMetadataStatement.get() as + | { last_sync_at: string | null; source_url: string | null } + | undefined; return { lastSyncAt: row?.last_sync_at ?? null, @@ -183,7 +188,9 @@ export class LocalAdvisoryDatabase { } getVulnerability(id: string): OsvVuln | null { - const row = this.getVulnerabilityStatement.get(id) as { osv_json: string } | undefined; + const row = this.getVulnerabilityStatement.get(id) as + | { osv_json: string } + | undefined; if (!row) return null; return JSON.parse(row.osv_json) as OsvVuln; @@ -194,7 +201,10 @@ export class LocalAdvisoryDatabase { return []; } - const rows = this.findMatchingIdsStatement.all(pkg.ecosystem, pkg.name) as AdvisoryRangeRow[]; + const rows = this.findMatchingIdsStatement.all( + pkg.ecosystem, + pkg.name, + ) as AdvisoryRangeRow[]; const ids = new Set(); for (const row of rows) { @@ -212,7 +222,11 @@ function versionMatchesRange(version: string, row: AdvisoryRangeRow): boolean { const fixed = row.fixed; const lastAffected = row.last_affected; - if (introduced && introduced !== "0" && compareVersions(version, introduced) < 0) { + if ( + introduced && + introduced !== "0" && + compareVersions(version, introduced) < 0 + ) { return false; } diff --git a/src/advisory/osv-advisory-source.ts b/src/advisory/osv-advisory-source.ts index 1195ed28..0d3f70d6 100644 --- a/src/advisory/osv-advisory-source.ts +++ b/src/advisory/osv-advisory-source.ts @@ -9,10 +9,13 @@ export class OsvAdvisorySource implements AdvisorySource { private readonly debugLog?: DebugLogger, ) {} - async queryBatch(packages: PackageRef[], meta?: { batchId?: string }): Promise { + async queryBatch( + packages: PackageRef[], + meta?: { batchId?: string }, + ): Promise { const requestUrl = `${this.baseUrl}/v1/querybatch`; const payload = { - queries: packages.map(p => ({ + queries: packages.map((p) => ({ package: { ecosystem: p.ecosystem, name: p.name, @@ -32,7 +35,7 @@ export class OsvAdvisorySource implements AdvisorySource { "Content-Type": "application/json", }, queryCount: packages.length, - sample: packages.slice(0, 3).map(p => ({ + sample: packages.slice(0, 3).map((p) => ({ ecosystem: p.ecosystem, name: p.name, version: p.version, @@ -59,7 +62,9 @@ export class OsvAdvisorySource implements AdvisorySource { } if (!response.ok) { - throw new Error(`OSV batch query failed: ${response.status} ${response.statusText}`); + throw new Error( + `OSV batch query failed: ${response.status} ${response.statusText}`, + ); } const data = await response.json(); @@ -76,9 +81,10 @@ export class OsvAdvisorySource implements AdvisorySource { method: "POST", url: requestUrl, durationMs: Date.now() - startedAt, - error: error instanceof Error - ? { message: error.message, stack: error.stack } - : String(error), + error: + error instanceof Error + ? { message: error.message, stack: error.stack } + : String(error), }); } const message = extractErrorMessage(error); @@ -110,7 +116,9 @@ export class OsvAdvisorySource implements AdvisorySource { } if (!response.ok) { - throw new Error(`OSV vuln fetch failed for ${id}: ${response.status} ${response.statusText}`); + throw new Error( + `OSV vuln fetch failed for ${id}: ${response.status} ${response.statusText}`, + ); } return response.json() as Promise; @@ -120,13 +128,16 @@ export class OsvAdvisorySource implements AdvisorySource { method: "GET", url: requestUrl, durationMs: Date.now() - startedAt, - error: error instanceof Error - ? { message: error.message, stack: error.stack } - : String(error), + error: + error instanceof Error + ? { message: error.message, stack: error.stack } + : String(error), }); } const message = extractErrorMessage(error); - throw new Error(`OSV vuln fetch failed for ${id} via ${this.baseUrl}: ${message}`); + throw new Error( + `OSV vuln fetch failed for ${id} via ${this.baseUrl}: ${message}`, + ); } } } diff --git a/src/advisory/osv-sync.ts b/src/advisory/osv-sync.ts index fccc1c75..7231ebbb 100644 --- a/src/advisory/osv-sync.ts +++ b/src/advisory/osv-sync.ts @@ -6,7 +6,8 @@ import type { OsvVuln } from "../types.js"; import { LocalAdvisoryDatabase } from "./local-db.js"; import { pluralize } from "../utils/string.js"; -const DEFAULT_OSV_NPM_DUMP_URL = "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip"; +const DEFAULT_OSV_NPM_DUMP_URL = + "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip"; export const ADVISORY_DB_STALE_AFTER_MS = 7 * 24 * 60 * 60 * 1000; export type SyncOsvAdvisoriesOptions = { @@ -66,7 +67,8 @@ export function getDefaultAdvisoryDbPath(outputPath?: string): string { } if (process.platform === "win32") { - const appData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local"); + const appData = + process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local"); return path.join(appData, "cve-lite", "advisories.db"); } @@ -95,7 +97,9 @@ export async function syncOsvAdvisories( const response = await fetchImpl(sourceUrl); if (!response.ok) { - throw new Error(`OSV dump download failed: ${response.status} ${response.statusText}`); + throw new Error( + `OSV dump download failed: ${response.status} ${response.statusText}`, + ); } const totalBytesHeader = response.headers.get("content-length"); @@ -124,7 +128,9 @@ export async function syncOsvAdvisories( await yieldToEventLoop(); const archiveEntries = unzipSync(zippedBytes); - const advisoryEntries = Object.entries(archiveEntries).filter(([entryName]) => entryName.endsWith(".json")); + const advisoryEntries = Object.entries(archiveEntries).filter(([entryName]) => + entryName.endsWith(".json"), + ); onProgress?.({ phase: "extract", totalEntries: Object.keys(archiveEntries).length, @@ -253,7 +259,7 @@ function formatBytes(bytes: number): string { } async function yieldToEventLoop(): Promise { - await new Promise(resolve => { + await new Promise((resolve) => { setImmediate(resolve); }); } diff --git a/src/cli/args.ts b/src/cli/args.ts index 9b20a90f..a9787c01 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -2,7 +2,11 @@ import type { CliCommand, ParsedOptions } from "../types.js"; import { ConfigAction, ConfigKey } from "./config.js"; export type ConfigSubcommand = - | { action: typeof ConfigAction.Set; key: typeof ConfigKey.CaCert; value: string } + | { + action: typeof ConfigAction.Set; + key: typeof ConfigKey.CaCert; + value: string; + } | { action: typeof ConfigAction.Unset; key: typeof ConfigKey.CaCert } | { action: typeof ConfigAction.Show }; @@ -16,7 +20,7 @@ export function parseArgs(argv: string[]): { failOn: "critical", batchSize: "100", searchDepth: "4", - minSeverity: "medium" + minSeverity: "medium", }; if (argv[0] === "advisories" && argv[1] === "sync") { @@ -77,21 +81,48 @@ export function parseArgs(argv: string[]): { if (argv[0] === "config") { const sub = argv[1]; if (sub === ConfigAction.Show) { - return { command: "config", options, configSubcommand: { action: ConfigAction.Show } }; + return { + command: "config", + options, + configSubcommand: { action: ConfigAction.Show }, + }; } if (sub === ConfigAction.Set) { const key = argv[2]; const value = argv[3]; - if (key !== ConfigKey.CaCert) throw new Error(`Unknown config key: ${key ?? "(none)"}. Valid keys: ${ConfigKey.CaCert}`); - if (!value) throw new Error(`cve-lite config set ${ConfigKey.CaCert} requires a path argument`); - return { command: "config", options, configSubcommand: { action: ConfigAction.Set, key: ConfigKey.CaCert, value } }; + if (key !== ConfigKey.CaCert) + throw new Error( + `Unknown config key: ${key ?? "(none)"}. Valid keys: ${ConfigKey.CaCert}`, + ); + if (!value) + throw new Error( + `cve-lite config set ${ConfigKey.CaCert} requires a path argument`, + ); + return { + command: "config", + options, + configSubcommand: { + action: ConfigAction.Set, + key: ConfigKey.CaCert, + value, + }, + }; } if (sub === ConfigAction.Unset) { const key = argv[2]; - if (key !== ConfigKey.CaCert) throw new Error(`Unknown config key: ${key ?? "(none)"}. Valid keys: ${ConfigKey.CaCert}`); - return { command: "config", options, configSubcommand: { action: ConfigAction.Unset, key: ConfigKey.CaCert } }; - } - throw new Error(`Unknown config subcommand: ${sub ?? "(none)"}. Use: ${ConfigAction.Set}, ${ConfigAction.Unset}, ${ConfigAction.Show}`); + if (key !== ConfigKey.CaCert) + throw new Error( + `Unknown config key: ${key ?? "(none)"}. Valid keys: ${ConfigKey.CaCert}`, + ); + return { + command: "config", + options, + configSubcommand: { action: ConfigAction.Unset, key: ConfigKey.CaCert }, + }; + } + throw new Error( + `Unknown config subcommand: ${sub ?? "(none)"}. Use: ${ConfigAction.Set}, ${ConfigAction.Unset}, ${ConfigAction.Show}`, + ); } let projectArg: string | undefined; diff --git a/src/cli/config-command.ts b/src/cli/config-command.ts index 3f8c7adb..7c15c7b2 100644 --- a/src/cli/config-command.ts +++ b/src/cli/config-command.ts @@ -1,5 +1,12 @@ import { chalk } from "../utils/chalk.js"; -import { readConfig, writeConfig, getConfigPath, validateCaCertFile, ConfigAction, ConfigKey } from "./config.js"; +import { + readConfig, + writeConfig, + getConfigPath, + validateCaCertFile, + ConfigAction, + ConfigKey, +} from "./config.js"; import type { ConfigSubcommand } from "./args.js"; export function runConfigCommand(sub: ConfigSubcommand): void { @@ -14,7 +21,9 @@ export function runConfigCommand(sub: ConfigSubcommand): void { console.log(chalk.gray(`Config file: ${getConfigPath()}`)); console.log(""); if (config.caCert) { - console.log(` ${chalk.bold(ConfigKey.CaCert)} ${chalk.cyan(config.caCert)}`); + console.log( + ` ${chalk.bold(ConfigKey.CaCert)} ${chalk.cyan(config.caCert)}`, + ); } return; } @@ -24,7 +33,9 @@ export function runConfigCommand(sub: ConfigSubcommand): void { const config = readConfig(); config.caCert = sub.value; writeConfig(config); - console.log(`${chalk.green("✔")} Saved: ${chalk.bold(ConfigKey.CaCert)} = ${chalk.cyan(sub.value)}`); + console.log( + `${chalk.green("✔")} Saved: ${chalk.bold(ConfigKey.CaCert)} = ${chalk.cyan(sub.value)}`, + ); console.log(chalk.gray(`Config file: ${getConfigPath()}`)); return; } diff --git a/src/cli/config.ts b/src/cli/config.ts index 2803a766..a604bdf4 100644 --- a/src/cli/config.ts +++ b/src/cli/config.ts @@ -62,7 +62,9 @@ export function validateCaCertFile(filePath: string): void { throw new Error(`file is empty: ${filePath}`); } if (!content.trimStart().startsWith("-----BEGIN")) { - throw new Error(`not a valid PEM certificate (expected file to start with -----BEGIN): ${filePath}`); + throw new Error( + `not a valid PEM certificate (expected file to start with -----BEGIN): ${filePath}`, + ); } } @@ -71,5 +73,9 @@ export function writeConfig(config: CliConfig): void { if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } - fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n", "utf8"); + fs.writeFileSync( + getConfigPath(), + JSON.stringify(config, null, 2) + "\n", + "utf8", + ); } diff --git a/src/cli/help.ts b/src/cli/help.ts index 31ee52c4..6edc93c5 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -12,7 +12,7 @@ const CLI_BANNER = [ `${chalk.green("✔")} Highlight critical issues`, `${chalk.green("✔")} Show a clear fix plan`, "", - chalk.gray("Fast. Local. Developer-first.") + chalk.gray("Fast. Local. Developer-first."), ].join("\n"); export function printBanner(options?: UpdateCheckOptions): void { @@ -81,7 +81,7 @@ export function printHelp(): void { " cve-lite . --report Generate an interactive HTML report", " cve-lite . --fix Apply validated direct dependency fixes and rescan", " cve-lite . --offline Scan using the local advisory database (no network)", - " cve-lite advisories sync Sync the local advisory database" + " cve-lite advisories sync Sync the local advisory database", ]; console.log(lines.join("\n")); } diff --git a/src/cli/validate.ts b/src/cli/validate.ts index fff3da9f..766af4d4 100644 --- a/src/cli/validate.ts +++ b/src/cli/validate.ts @@ -42,7 +42,9 @@ export function validateOptions(options: ParsedOptions): void { try { validateCaCertFile(options.caCert); } catch (err) { - throw new Error(`--ca-cert: ${err instanceof Error ? err.message : String(err)}`); + throw new Error( + `--ca-cert: ${err instanceof Error ? err.message : String(err)}`, + ); } } } diff --git a/src/constants.ts b/src/constants.ts index ff7bb665..ddeb78cb 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -6,7 +6,7 @@ export const severityOrder: Record = { medium: 2, high: 3, critical: 4, - unknown: 1 + unknown: 1, }; export const DEFAULT_BATCH_SIZE = 100; @@ -47,5 +47,5 @@ export const EXCLUDED_DIRS = new Set([ ".next", ".turbo", ".angular", - ".nx" + ".nx", ]); diff --git a/src/docs/CONTRIBUTING.md b/src/docs/CONTRIBUTING.md index 385abfd4..04d1b565 100644 --- a/src/docs/CONTRIBUTING.md +++ b/src/docs/CONTRIBUTING.md @@ -58,7 +58,7 @@ Conventions used across the codebase: - ES modules (`import` / `export`), no `require`. Source files end in `.ts` and import other modules with explicit `.js` extensions on relative paths (Node ESM resolution). - camelCase for functions and variables, PascalCase for types and classes, kebab-case for filenames. - One concern per file. Modules in `src/` map to a focused responsibility — parser, scanner, remediation step, output formatter — and changes that mix unrelated concerns are typically asked to be split. -- Comments are written only when the *why* is non-obvious: a hidden constraint, a subtle invariant, or a workaround for a specific bug. Comments that just describe what the code does are removed during review. +- Comments are written only when the _why_ is non-obvious: a hidden constraint, a subtle invariant, or a workaround for a specific bug. Comments that just describe what the code does are removed during review. - Output and security language must be precise. Avoid words like "fully safe", "guaranteed compatible", or "stable version" in user-visible text. Findings are based on advisory data, not exploitability or runtime reachability claims. - Every finding should aim to produce a runnable command where the data supports it. Vague guidance like "upgrade the parent dependency chain" without naming the parent is treated as a regression. diff --git a/src/index.ts b/src/index.ts index 2c98f3aa..295ed17b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,10 +5,18 @@ import { parseArgs } from "./cli/args.js"; import { printBanner, printHelp } from "./cli/help.js"; import { validateOptions } from "./cli/validate.js"; import { loadPackages, buildNoPackagesMessage } from "./parsers/index.js"; -import { scanPackages, buildCoverageNotes, createAdvisorySource } from "./scanner.js"; +import { + scanPackages, + buildCoverageNotes, + createAdvisorySource, +} from "./scanner.js"; import { syncOsvAdvisories } from "./advisory/osv-sync.js"; import { normalizeSeverity } from "./osv/severity.js"; -import { DEFAULT_BATCH_SIZE, DEFAULT_SEARCH_DEPTH, severityOrder } from "./constants.js"; +import { + DEFAULT_BATCH_SIZE, + DEFAULT_SEARCH_DEPTH, + severityOrder, +} from "./constants.js"; import { chalk } from "./utils/chalk.js"; import { createSpinner } from "./output/spinner.js"; import { createDebugLogger, type DebugLogger } from "./output/debug.js"; @@ -35,7 +43,7 @@ import { logInfo, logWarn, printCacheSummary, - sortFindingsForOutput + sortFindingsForOutput, } from "./output/formatters.js"; import { countBySeverity } from "./utils/severity.js"; import { buildReportData, writeHtmlReport } from "./output/html-reporter.js"; @@ -50,7 +58,7 @@ import { printSkippedDependencies, printTable, printFinalStatus, - printCompactOutput + printCompactOutput, } from "./output/printers.js"; import { installSkill } from "./skills/install.js"; import { readConfig, validateCaCertFile } from "./cli/config.js"; @@ -61,13 +69,20 @@ import { FixExecutionResult, printFixModeSummary, } from "./utils/fix-runner.js"; -import { hasRootLockfile, findNestedLockfiles } from "./parsers/multi-package.js"; +import { + hasRootLockfile, + findNestedLockfiles, +} from "./parsers/multi-package.js"; import { handleMultiFolderScan } from "./scan/multi-folder-scan.js"; import { createPullRequestForFixes, findingsMeetFailOnThreshold, } from "./utils/create-pr.js"; -import { readBaseline, writeBaseline, filterNewFindings } from "./utils/baseline.js"; +import { + readBaseline, + writeBaseline, + filterNewFindings, +} from "./utils/baseline.js"; let parsedArgs: ReturnType | null = null; try { parsedArgs = parseArgs(process.argv.slice(2)); @@ -89,392 +104,499 @@ if (parsedArgs) { printBanner(options); process.exit(0); } else { - const projectPath = path.resolve(projectArg || "."); - const batchSize = Number(options.batchSize || DEFAULT_BATCH_SIZE); - const searchDepth = Math.max(0, Number(options.searchDepth || DEFAULT_SEARCH_DEPTH)); - const debugSession = createDebugLogger(!!options.debug); - const debugLog = debugSession.log; - const scanStartedAt = Date.now(); - - async function main() { - printBanner(options); - debugSession.announcePath(); - - debugLog("CLI started", { - version: cliVersion, - args: process.argv.slice(2), - }); - - if (command === "config") { - const { configSubcommand } = parsedArgs!; - if (!configSubcommand) { - console.error(chalk.red("Error: config requires a subcommand: set, unset, show")); - console.error(chalk.gray("Run `cve-lite --help` for usage.")); - process.exit(1); - } - try { - runConfigCommand(configSubcommand); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(chalk.red(`Error: ${message}`)); - process.exit(1); - } - process.exit(0); - } + const projectPath = path.resolve(projectArg || "."); + const batchSize = Number(options.batchSize || DEFAULT_BATCH_SIZE); + const searchDepth = Math.max( + 0, + Number(options.searchDepth || DEFAULT_SEARCH_DEPTH), + ); + const debugSession = createDebugLogger(!!options.debug); + const debugLog = debugSession.log; + const scanStartedAt = Date.now(); + + async function main() { + printBanner(options); + debugSession.announcePath(); + + debugLog("CLI started", { + version: cliVersion, + args: process.argv.slice(2), + }); - const savedConfig = readConfig(); - const resolvedCaCert = options.caCert ?? savedConfig.caCert; - if (resolvedCaCert) { - if (!options.caCert && savedConfig.caCert) { + if (command === "config") { + const { configSubcommand } = parsedArgs!; + if (!configSubcommand) { + console.error( + chalk.red("Error: config requires a subcommand: set, unset, show"), + ); + console.error(chalk.gray("Run `cve-lite --help` for usage.")); + process.exit(1); + } try { - validateCaCertFile(resolvedCaCert); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error(chalk.red(`Error: Saved ca-cert is no longer valid: ${message}`)); - console.error(chalk.gray(`Run \`cve-lite config unset ca-cert\` to remove it.`)); + runConfigCommand(configSubcommand); + } catch (error) { + const message = + error instanceof Error ? error.message : String(error); + console.error(chalk.red(`Error: ${message}`)); process.exit(1); } + process.exit(0); } - process.env.NODE_EXTRA_CA_CERTS = resolvedCaCert; - } - debugLog("Config loaded", { - caCert: resolvedCaCert ?? null, - nodeExtraCaCerts: process.env.NODE_EXTRA_CA_CERTS ?? null, - }); - if (command === "advisories-sync") { - const spinner = createSpinner("Preparing advisory sync...", options); - const usePlainProgressLogs = !process.stdout.isTTY || !!options.json; - const result = await syncOsvAdvisories({ - outputPath: options.output, - onProgress: event => { - if (event.phase === "complete") { - return; + const savedConfig = readConfig(); + const resolvedCaCert = options.caCert ?? savedConfig.caCert; + if (resolvedCaCert) { + if (!options.caCert && savedConfig.caCert) { + try { + validateCaCertFile(resolvedCaCert); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error( + chalk.red(`Error: Saved ca-cert is no longer valid: ${message}`), + ); + console.error( + chalk.gray(`Run \`cve-lite config unset ca-cert\` to remove it.`), + ); + process.exit(1); } - if (usePlainProgressLogs) { - console.log(event.message); - return; - } - spinner.update(event.message); - }, + } + process.env.NODE_EXTRA_CA_CERTS = resolvedCaCert; + } + debugLog("Config loaded", { + caCert: resolvedCaCert ?? null, + nodeExtraCaCerts: process.env.NODE_EXTRA_CA_CERTS ?? null, }); - if (usePlainProgressLogs) { - console.log(`Advisory sync complete (${result.advisoryCount} records)`); - } else { - spinner.succeed(`Advisory sync complete (${result.advisoryCount} records)`); + + if (command === "advisories-sync") { + const spinner = createSpinner("Preparing advisory sync...", options); + const usePlainProgressLogs = !process.stdout.isTTY || !!options.json; + const result = await syncOsvAdvisories({ + outputPath: options.output, + onProgress: (event) => { + if (event.phase === "complete") { + return; + } + if (usePlainProgressLogs) { + console.log(event.message); + return; + } + spinner.update(event.message); + }, + }); + if (usePlainProgressLogs) { + console.log( + `Advisory sync complete (${result.advisoryCount} records)`, + ); + } else { + spinner.succeed( + `Advisory sync complete (${result.advisoryCount} records)`, + ); + } + console.log( + `${chalk.gray("Advisory database:")} synced ${result.advisoryCount} ${pluralize(result.advisoryCount, "record")} to ${chalk.cyan(result.dbPath)}`, + ); + process.exit(0); + return; } - console.log( - `${chalk.gray("Advisory database:")} synced ${result.advisoryCount} ${pluralize(result.advisoryCount, "record")} to ${chalk.cyan(result.dbPath)}`, - ); - process.exit(0); - return; - } - if (command === "install-skill") { - installSkill(process.cwd()); - process.exit(0); - return; - } + if (command === "install-skill") { + installSkill(process.cwd()); + process.exit(0); + return; + } - validateOptions(options); + validateOptions(options); - // Multi-folder mode: if no root lockfile and 2+ nested lockfiles exist, - // route to dedicated multi-folder handler instead of single-lockfile scan - const nestedLockfiles = findNestedLockfiles(projectPath, searchDepth); - if (!hasRootLockfile(projectPath) && nestedLockfiles.length >= 2) { - await handleMultiFolderScan({ projectRoot: projectPath, batchSize, options }); - return; - } + // Multi-folder mode: if no root lockfile and 2+ nested lockfiles exist, + // route to dedicated multi-folder handler instead of single-lockfile scan + const nestedLockfiles = findNestedLockfiles(projectPath, searchDepth); + if (!hasRootLockfile(projectPath) && nestedLockfiles.length >= 2) { + await handleMultiFolderScan({ + projectRoot: projectPath, + batchSize, + options, + }); + return; + } - let advisorySourceLine = ""; - let advisoryDbFreshnessLine: string | null = null; - let advisoryDbWarning: string | null = null; - try { - const advisorySource = createAdvisorySource({ - osvUrl: options.osvUrl, - offline: options.offline, - offlineDb: options.offlineDb, - debugLog, - }); - advisorySourceLine = advisorySource.sourceLabel; - debugLog("Advisory source", { - mode: advisorySource.offline ? "offline" : "online", - url: options.osvUrl ?? (advisorySource.offline ? null : "https://api.osv.dev"), - label: advisorySourceLine, - }); - if (advisorySource.offline) { - const metadata = advisorySource.advisoryDbMetadata; - advisoryDbFreshnessLine = formatAdvisoryDbFreshness(metadata?.lastSyncAt ?? null); - if (advisorySource.advisoryDbIsStale) { - advisoryDbWarning = metadata?.lastSyncAt - ? "The local advisory DB appears stale. Re-run `cve-lite advisories sync` to refresh it." - : "The local advisory DB has no recorded sync timestamp. Re-run `cve-lite advisories sync` to refresh it."; + let advisorySourceLine = ""; + let advisoryDbFreshnessLine: string | null = null; + let advisoryDbWarning: string | null = null; + try { + const advisorySource = createAdvisorySource({ + osvUrl: options.osvUrl, + offline: options.offline, + offlineDb: options.offlineDb, + debugLog, + }); + advisorySourceLine = advisorySource.sourceLabel; + debugLog("Advisory source", { + mode: advisorySource.offline ? "offline" : "online", + url: + options.osvUrl ?? + (advisorySource.offline ? null : "https://api.osv.dev"), + label: advisorySourceLine, + }); + if (advisorySource.offline) { + const metadata = advisorySource.advisoryDbMetadata; + advisoryDbFreshnessLine = formatAdvisoryDbFreshness( + metadata?.lastSyncAt ?? null, + ); + if (advisorySource.advisoryDbIsStale) { + advisoryDbWarning = metadata?.lastSyncAt + ? "The local advisory DB appears stale. Re-run `cve-lite advisories sync` to refresh it." + : "The local advisory DB has no recorded sync timestamp. Re-run `cve-lite advisories sync` to refresh it."; + } } + advisorySource.cleanup(); + } catch (error) { + if (options.offline || options.offlineDb) { + const reason = error instanceof Error ? error.message : String(error); + throw new Error( + `Offline advisory database is not available: ${reason}\n${offlineDbSyncHint(options.offlineDb).join("\n")}`, + ); + } + throw error; } - advisorySource.cleanup(); - } catch (error) { - if (options.offline || options.offlineDb) { - const reason = error instanceof Error ? error.message : String(error); - throw new Error(`Offline advisory database is not available: ${reason}\n${offlineDbSyncHint(options.offlineDb).join("\n")}`); - } - throw error; - } - if (!options.json && !options.ratchet) { - if (options.offline || options.offlineDb) { - console.log(chalk.gray("Offline mode:") + " " + chalk.yellow("enabled") + " " + chalk.gray("(no external advisory calls will be made)")); - } - if (advisorySourceLine) { - console.log(`${chalk.gray("Advisory source:")} ${formatAdvisorySourceLine(advisorySourceLine)}`); + if (!options.json && !options.ratchet) { + if (options.offline || options.offlineDb) { + console.log( + chalk.gray("Offline mode:") + + " " + + chalk.yellow("enabled") + + " " + + chalk.gray("(no external advisory calls will be made)"), + ); + } + if (advisorySourceLine) { + console.log( + `${chalk.gray("Advisory source:")} ${formatAdvisorySourceLine(advisorySourceLine)}`, + ); + } + if (advisoryDbFreshnessLine) { + console.log( + `${chalk.gray("Advisory DB freshness:")} ${advisoryDbFreshnessLine}`, + ); + } } - if (advisoryDbFreshnessLine) { - console.log(`${chalk.gray("Advisory DB freshness:")} ${advisoryDbFreshnessLine}`); + if (advisoryDbWarning) { + logWarn(advisoryDbWarning, options); } - } - if (advisoryDbWarning) { - logWarn(advisoryDbWarning, options); - } - let scanInput = loadPackages(projectPath, !!options.prodOnly, searchDepth); - let packages = scanInput.packages; - if (scanInput.filePath) { - debugLog("Lockfile selected", { + let scanInput = loadPackages( + projectPath, + !!options.prodOnly, + searchDepth, + ); + let packages = scanInput.packages; + if (scanInput.filePath) { + debugLog("Lockfile selected", { + source: scanInput.source, + path: scanInput.filePath, + }); + } + debugLog("Packages parsed", { + count: packages.length, source: scanInput.source, - path: scanInput.filePath, }); - } - debugLog("Packages parsed", { - count: packages.length, - source: scanInput.source, - }); - if (!options.ratchet) { - logInfo( - `Parsed ${packages.length} ${pluralize(packages.length, "package")} from ${scanInput.source}${ - scanInput.filePath ? ` (${path.relative(projectPath, scanInput.filePath) || path.basename(scanInput.filePath)})` : "" - }`, - options - ); - printCacheSummary(options.cacheDir, options); - } + if (!options.ratchet) { + logInfo( + `Parsed ${packages.length} ${pluralize(packages.length, "package")} from ${scanInput.source}${ + scanInput.filePath + ? ` (${path.relative(projectPath, scanInput.filePath) || path.basename(scanInput.filePath)})` + : "" + }`, + options, + ); + printCacheSummary(options.cacheDir, options); + } - if (scanInput.warnings.length > 0) { - for (const warning of scanInput.warnings) { - logWarn(warning, options); + if (scanInput.warnings.length > 0) { + for (const warning of scanInput.warnings) { + logWarn(warning, options); + } } - } - if (packages.length === 0) { - debugLog("Scan skipped", { reason: "no packages found", projectPath }); - logWarn(buildNoPackagesMessage(projectPath), options); - process.exit(0); - return; - } + if (packages.length === 0) { + debugLog("Scan skipped", { reason: "no packages found", projectPath }); + logWarn(buildNoPackagesMessage(projectPath), options); + process.exit(0); + return; + } - if (!options.json && !options.ratchet) console.log(); - let scanState = await scanProject({ - scanInput, - batchSize, - options, - projectPath, - debugLog, - }); - const findingsBeforeFixList = scanState.sorted; - const findingsBeforeFix = findingsBeforeFixList.length; - let fixResult: FixExecutionResult | null = null; - let baseline = readBaseline(projectArg ?? "."); - let suppressedCount = 0; - - if (options.fix) { - fixResult = await applyFixesIfRequested({ - plan: scanState.suggestedFixCommands, - projectPath, - totalFindings: scanState.sorted.length, + if (!options.json && !options.ratchet) console.log(); + let scanState = await scanProject({ + scanInput, + batchSize, options, + projectPath, debugLog, }); + const findingsBeforeFixList = scanState.sorted; + const findingsBeforeFix = findingsBeforeFixList.length; + let fixResult: FixExecutionResult | null = null; + let baseline = readBaseline(projectArg ?? "."); + let suppressedCount = 0; + + if (options.fix) { + fixResult = await applyFixesIfRequested({ + plan: scanState.suggestedFixCommands, + projectPath, + totalFindings: scanState.sorted.length, + options, + debugLog, + }); + + if (fixResult.appliedFixCount > 0) { + console.log( + `${chalk.cyan("⠋")} ${chalk.gray("Rescanning project...")}`, + ); + scanInput = loadPackages( + projectPath, + !!options.prodOnly, + searchDepth, + ); + packages = scanInput.packages; + if (packages.length === 0) { + debugLog("Scan skipped", { + reason: "no packages found after fix rescan", + projectPath, + }); + logWarn(buildNoPackagesMessage(projectPath), options); + process.exit(0); + return; + } + + scanState = await scanProject({ + scanInput, + batchSize, + options, + projectPath, + debugLog, + }); + } + } + + if (options.fix) { + printFixModeSummary({ + fixResult, + findingsBeforeFix, + findingsAfterFix: scanState.sorted.length, + remainingBySeverity: countBySeverity(scanState.sorted), + }); - if (fixResult.appliedFixCount > 0) { - console.log(`${chalk.cyan("⠋")} ${chalk.gray("Rescanning project...")}`); - scanInput = loadPackages(projectPath, !!options.prodOnly, searchDepth); - packages = scanInput.packages; - if (packages.length === 0) { - debugLog("Scan skipped", { reason: "no packages found after fix rescan", projectPath }); - logWarn(buildNoPackagesMessage(projectPath), options); + if (options.createPr && fixResult) { + if (fixResult.appliedFixCount === 0) { + logWarn( + "Skipping pull request creation: no direct fixes were applied.", + options, + ); + } else { + console.log(""); + console.log(chalk.bold.cyan("Creating pull request (--create-pr)")); + const prResult = await createPullRequestForFixes({ + projectPath, + baseBranch: options.prBase ?? "main", + fixResult, + findingsBeforeFix: findingsBeforeFixList, + findingsAfterFix: scanState.sorted, + }); + if (prResult.skipped) { + logWarn( + prResult.skipReason ?? "Pull request was not created.", + options, + ); + } else if (prResult.prUrl) { + console.log( + `${chalk.gray("Pull request:")} ${chalk.cyan(prResult.prUrl)}`, + ); + console.log( + `${chalk.gray("Branch:")} ${chalk.cyan(prResult.branchName)}`, + ); + } else { + logWarn( + `Branch ${prResult.branchName} was pushed, but no pull request URL was returned.`, + options, + ); + } + } + } + } else { + // --ratchet: save baseline and exit 0 + if (options.ratchet) { + writeBaseline(projectArg ?? ".", scanState.sorted); + const count = scanState.sorted.length; + console.log( + chalk.green( + `✓ Baseline saved to .cve-lite/baseline.json with ${count} ${count === 1 ? "finding" : "findings"}. Future scans will only report findings above this baseline.`, + ), + ); process.exit(0); return; } - scanState = await scanProject({ - scanInput, - batchSize, + // auto-apply baseline if it exists - filter before output + if (baseline) { + const filtered = filterNewFindings(scanState.sorted, baseline); + scanState.sorted = filtered.newFindings; + scanState.tableFindings = scanState.tableFindings.filter((f) => + filtered.newFindings.some( + (nf) => + nf.pkg.name === f.pkg.name && nf.pkg.version === f.pkg.version, + ), + ); + suppressedCount = filtered.suppressedCount; + } + + await writeOutputs( options, + { + sorted: scanState.sorted, + allPackages: scanState.allPackages, + suggestedFixCommands: scanState.suggestedFixCommands, + coverage: scanState.coverage, + minSeverity: scanState.minSeverity, + tableFindings: scanState.tableFindings, + }, + scanInput, projectPath, - debugLog, - }); - } - } - - if (options.fix) { - printFixModeSummary({ - fixResult, - findingsBeforeFix, - findingsAfterFix: scanState.sorted.length, - remainingBySeverity: countBySeverity(scanState.sorted), - }); + ); - if (options.createPr && fixResult) { - if (fixResult.appliedFixCount === 0) { - logWarn("Skipping pull request creation: no direct fixes were applied.", options); - } else { - console.log(""); - console.log(chalk.bold.cyan("Creating pull request (--create-pr)")); - const prResult = await createPullRequestForFixes({ - projectPath, - baseBranch: options.prBase ?? "main", - fixResult, - findingsBeforeFix: findingsBeforeFixList, - findingsAfterFix: scanState.sorted, - }); - if (prResult.skipped) { - logWarn(prResult.skipReason ?? "Pull request was not created.", options); - } else if (prResult.prUrl) { - console.log(`${chalk.gray("Pull request:")} ${chalk.cyan(prResult.prUrl)}`); - console.log(`${chalk.gray("Branch:")} ${chalk.cyan(prResult.branchName)}`); + if ( + !(options.json || options.sarif || options.cdx) || + options.verbose + ) { + const offline = !!options.offline || !!options.offlineDb; + if (options.verbose) { + printSummary(scanState.sorted, packages.length, scanInput); + printActionSummary(scanState.sorted); + printSuggestedFixCommands(scanState.sorted, scanInput, { offline }); + printSuggestedFixCommandSkips(scanState.sorted, scanInput, { + offline, + }); + if (scanInput.skippedDependencies.length > 0) { + printSkippedDependencies(scanInput.skippedDependencies); + } + if (scanState.sorted.length > 0) { + if (scanState.tableFindings.length > 0) { + const skippedKeys = new Set( + (scanState.suggestedFixCommands?.skipped ?? []).map( + (s) => `${s.package}@${s.version}`, + ), + ); + printTable( + scanState.tableFindings, + options.all ? null : scanState.minSeverity, + skippedKeys, + ); + } else { + logInfo( + `No findings met the table threshold of ${scanState.minSeverity}. Re-run with --all to show everything.`, + options, + ); + } + } + printCoverage([...scanInput.notes, ...scanState.coverage]); + printFinalStatus(scanState.sorted); } else { - logWarn(`Branch ${prResult.branchName} was pushed, but no pull request URL was returned.`, options); + printCompactOutput(scanState.sorted, scanInput, { + offline, + all: !!options.all, + }); } } } - } else { - // --ratchet: save baseline and exit 0 - if (options.ratchet) { - writeBaseline(projectArg ?? ".", scanState.sorted); - const count = scanState.sorted.length; - console.log(chalk.green(`✓ Baseline saved to .cve-lite/baseline.json with ${count} ${count === 1 ? "finding" : "findings"}. Future scans will only report findings above this baseline.`)); - process.exit(0); - return; - } - // auto-apply baseline if it exists - filter before output - if (baseline) { - const filtered = filterNewFindings(scanState.sorted, baseline); - scanState.sorted = filtered.newFindings; - scanState.tableFindings = scanState.tableFindings.filter(f => - filtered.newFindings.some(nf => nf.pkg.name === f.pkg.name && nf.pkg.version === f.pkg.version) + if (options.report) { + const outputDir = path.resolve( + typeof options.report === "string" ? options.report : "./cve-report", ); - suppressedCount = filtered.suppressedCount; + const reportData = buildReportData({ + projectPath, + cliVersion, + packageManager: scanInput.source, + lockfileSource: scanInput.filePath + ? path.basename(scanInput.filePath) + : scanInput.source, + packageCount: packages.length, + findings: scanState.sorted, + suggestedFixCommands: scanState.suggestedFixCommands, + notes: [...scanInput.notes, ...scanState.coverage], + warnings: scanInput.warnings, + }); + const { reportPath } = await writeHtmlReport({ + outputDir, + data: reportData, + autoOpen: !options.noOpen, + }); + console.log(`${chalk.gray("Report:")} ${chalk.cyan(reportPath)}`); } - await writeOutputs(options, { - sorted: scanState.sorted, - allPackages: scanState.allPackages, - suggestedFixCommands: scanState.suggestedFixCommands, - coverage: scanState.coverage, - minSeverity: scanState.minSeverity, - tableFindings: scanState.tableFindings, - }, scanInput, projectPath); - - if (!(options.json || options.sarif || options.cdx) || options.verbose) { - const offline = !!options.offline || !!options.offlineDb; - if (options.verbose) { - printSummary(scanState.sorted, packages.length, scanInput); - printActionSummary(scanState.sorted); - printSuggestedFixCommands(scanState.sorted, scanInput, { offline }); - printSuggestedFixCommandSkips(scanState.sorted, scanInput, { offline }); - if (scanInput.skippedDependencies.length > 0) { - printSkippedDependencies(scanInput.skippedDependencies); - } - if (scanState.sorted.length > 0) { - if (scanState.tableFindings.length > 0) { - const skippedKeys = new Set( - (scanState.suggestedFixCommands?.skipped ?? []).map(s => `${s.package}@${s.version}`) - ); - printTable(scanState.tableFindings, options.all ? null : scanState.minSeverity, skippedKeys); - } else { - logInfo(`No findings met the table threshold of ${scanState.minSeverity}. Re-run with --all to show everything.`, options); - } - } - printCoverage([...scanInput.notes, ...scanState.coverage]); - printFinalStatus(scanState.sorted); + debugLog("Scan finished", { + totalDurationMs: Date.now() - scanStartedAt, + findings: scanState.sorted.length, + packages: packages.length, + }); + + if (baseline) { + if (scanState.sorted.length === 0) { + console.log( + chalk.green( + `No new findings above baseline - ${suppressedCount} existing ${suppressedCount === 1 ? "finding" : "findings"} suppressed`, + ), + ); } else { - printCompactOutput(scanState.sorted, scanInput, { offline, all: !!options.all }); + console.log( + chalk.yellow( + `${scanState.sorted.length} new ${scanState.sorted.length === 1 ? "finding" : "findings"} above baseline - ${suppressedCount} existing ${suppressedCount === 1 ? "finding" : "findings"} suppressed`, + ), + ); } } - } - if (options.report) { - const outputDir = path.resolve( - typeof options.report === "string" ? options.report : "./cve-report" + const failLevel = normalizeSeverity(options.failOn); + const shouldFail = scanState.sorted.some( + (f) => severityOrder[f.severity] >= severityOrder[failLevel], ); - const reportData = buildReportData({ - projectPath, - cliVersion, - packageManager: scanInput.source, - lockfileSource: scanInput.filePath ? path.basename(scanInput.filePath) : scanInput.source, - packageCount: packages.length, - findings: scanState.sorted, - suggestedFixCommands: scanState.suggestedFixCommands, - notes: [...scanInput.notes, ...scanState.coverage], - warnings: scanInput.warnings, - }); - const { reportPath } = await writeHtmlReport({ - outputDir, - data: reportData, - autoOpen: !options.noOpen, - }); - console.log(`${chalk.gray("Report:")} ${chalk.cyan(reportPath)}`); + process.exit(shouldFail ? 1 : 0); + return; } - debugLog("Scan finished", { - totalDurationMs: Date.now() - scanStartedAt, - findings: scanState.sorted.length, - packages: packages.length, - }); - - if (baseline) { - if (scanState.sorted.length === 0) { - console.log(chalk.green(`No new findings above baseline - ${suppressedCount} existing ${suppressedCount === 1 ? "finding" : "findings"} suppressed`)); + main().catch((error) => { + const errorMessage = + error instanceof Error ? error.message : String(error); + console.error(chalk.red(`Error: ${errorMessage}`)); + if (options.debug && error instanceof Error && error.stack) { + debugLog("Unhandled error", { + message: error.message, + stack: error.stack, + }); + } + if (isSslCertificateError(error)) { + const [hint, ...rest] = sslCertificateErrorHint(); + console.error(chalk.yellow(hint)); + rest.forEach((line) => console.error(chalk.gray(line))); + } else if (isRateLimitError(errorMessage)) { + const [hint, ...rest] = rateLimitAdvisoryRequestHint(); + console.error(chalk.yellow(hint)); + rest.forEach((line) => console.error(chalk.gray(line))); + } else if (isServerError(errorMessage)) { + const [hint, ...rest] = serverAdvisoryRequestHint(); + console.error(chalk.yellow(hint)); + rest.forEach((line) => console.error(chalk.gray(line))); + } else if (isLikelyBlockedAdvisoryRequestError(errorMessage)) { + const [hint, ...rest] = blockedAdvisoryRequestHint(); + console.error(chalk.yellow(hint)); + rest.forEach((line) => console.error(chalk.gray(line))); } else { - console.log(chalk.yellow(`${scanState.sorted.length} new ${scanState.sorted.length === 1 ? "finding" : "findings"} above baseline - ${suppressedCount} existing ${suppressedCount === 1 ? "finding" : "findings"} suppressed`)); + const [hint, ...rest] = fetchErrorCaCertHint(); + console.error(chalk.yellow(hint)); + rest.forEach((line) => console.error(chalk.gray(line))); } - } - - const failLevel = normalizeSeverity(options.failOn); - const shouldFail = scanState.sorted.some(f => severityOrder[f.severity] >= severityOrder[failLevel]); - process.exit(shouldFail ? 1 : 0); - return; - } - - main().catch((error) => { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error(chalk.red(`Error: ${errorMessage}`)); - if (options.debug && error instanceof Error && error.stack) { - debugLog("Unhandled error", { message: error.message, stack: error.stack }); - } - if (isSslCertificateError(error)) { - const [hint, ...rest] = sslCertificateErrorHint(); - console.error(chalk.yellow(hint)); - rest.forEach(line => console.error(chalk.gray(line))); - } else if (isRateLimitError(errorMessage)) { - const [hint, ...rest] = rateLimitAdvisoryRequestHint(); - console.error(chalk.yellow(hint)); - rest.forEach(line => console.error(chalk.gray(line))); - } else if (isServerError(errorMessage)) { - const [hint, ...rest] = serverAdvisoryRequestHint(); - console.error(chalk.yellow(hint)); - rest.forEach(line => console.error(chalk.gray(line))); - } else if (isLikelyBlockedAdvisoryRequestError(errorMessage)) { - const [hint, ...rest] = blockedAdvisoryRequestHint(); - console.error(chalk.yellow(hint)); - rest.forEach(line => console.error(chalk.gray(line))); - } else { - const [hint, ...rest] = fetchErrorCaCertHint(); - console.error(chalk.yellow(hint)); - rest.forEach(line => console.error(chalk.gray(line))); - } - process.exit(1); - }); + process.exit(1); + }); } } @@ -485,17 +607,26 @@ async function scanProject(params: { projectPath: string; debugLog: DebugLogger; }) { - const directDependencyNames = readDirectDependencyNames(params.projectPath, !!params.options.prodOnly); - const findings = await scanPackages(params.scanInput.packages, params.batchSize, params.options, { - directDependencyNames, - scanSource: params.scanInput.source, - scanFilePath: params.scanInput.filePath, - }, params.debugLog); + const directDependencyNames = readDirectDependencyNames( + params.projectPath, + !!params.options.prodOnly, + ); + const findings = await scanPackages( + params.scanInput.packages, + params.batchSize, + params.options, + { + directDependencyNames, + scanSource: params.scanInput.source, + scanFilePath: params.scanInput.filePath, + }, + params.debugLog, + ); if (params.options.usage) { logInfo(`Scanning project source for usage hints...`, params.options); const usageStartedAt = Date.now(); - const pkgNames = new Set(findings.map(f => f.pkg.name)); + const pkgNames = new Set(findings.map((f) => f.pkg.name)); const usageData = scanProjectForPackageUsage(params.projectPath, pkgNames); let matchedPackages = 0; for (const finding of findings) { @@ -521,7 +652,7 @@ async function scanProject(params: { let finalFindings = findings; if (params.options.onlyUsed) { const beforeCount = finalFindings.length; - finalFindings = finalFindings.filter(f => f.usage?.imported); + finalFindings = finalFindings.filter((f) => f.usage?.imported); if (params.options.debug) { params.debugLog("Findings filtered", { reason: "only-used", @@ -538,7 +669,11 @@ async function scanProject(params: { const tableFindings = params.options.all ? sorted : selectFindingsForTable(sorted, minSeverity); - const suggestedFixCommands = buildSuggestedFixCommandPlan(sorted, params.scanInput, { offline }); + const suggestedFixCommands = buildSuggestedFixCommandPlan( + sorted, + params.scanInput, + { offline }, + ); return { sorted, @@ -549,4 +684,3 @@ async function scanProject(params: { allPackages: params.scanInput.packages, }; } - diff --git a/src/osv/cache.ts b/src/osv/cache.ts index f5dfc742..3b4be137 100644 --- a/src/osv/cache.ts +++ b/src/osv/cache.ts @@ -5,7 +5,12 @@ import type { CacheFile, QueryCacheEntry } from "../types.js"; import type { DebugLogger } from "../output/debug.js"; function createEmptyCache(): CacheFile { - return { version: 3, createdAt: new Date().toISOString(), entries: {}, queryEntries: {} }; + return { + version: 3, + createdAt: new Date().toISOString(), + entries: {}, + queryEntries: {}, + }; } export function getCacheFilePath(cacheDirOverride?: string): string { @@ -17,11 +22,17 @@ export function getCacheFilePath(cacheDirOverride?: string): string { return path.join(baseDir, "osv-vulns.json"); } -export function isEntryStale(entry: { cachedAt: string }, nowMs: number): boolean { +export function isEntryStale( + entry: { cachedAt: string }, + nowMs: number, +): boolean { return nowMs - new Date(entry.cachedAt).getTime() > 30 * 60 * 1000; } -export function loadCache(cacheDirOverride?: string, debugLog?: DebugLogger): CacheFile { +export function loadCache( + cacheDirOverride?: string, + debugLog?: DebugLogger, +): CacheFile { const filePath = getCacheFilePath(cacheDirOverride); if (!fs.existsSync(filePath)) { debugLog?.("Cache loaded", { @@ -34,25 +45,42 @@ export function loadCache(cacheDirOverride?: string, debugLog?: DebugLogger): Ca } try { - const parsed = JSON.parse(fs.readFileSync(filePath, "utf8")) as Record; + const parsed = JSON.parse(fs.readFileSync(filePath, "utf8")) as Record< + string, + unknown + >; if (typeof parsed !== "object" || parsed === null) { - debugLog?.("Cache load failed", { path: filePath, reason: "invalid root object" }); + debugLog?.("Cache load failed", { + path: filePath, + reason: "invalid root object", + }); return createEmptyCache(); } - const entries = (parsed.entries && typeof parsed.entries === "object") - ? parsed.entries as CacheFile["entries"] - : {}; + const entries = + parsed.entries && typeof parsed.entries === "object" + ? (parsed.entries as CacheFile["entries"]) + : {}; // v2: queryEntries values are string[] — migrate to { vulnIds, cachedAt: epoch } if (parsed.version === 2) { - const rawQuery = parsed.queryEntries as Record | undefined; + const rawQuery = parsed.queryEntries as + | Record + | undefined; const queryEntries: CacheFile["queryEntries"] = {}; for (const [key, vulnIds] of Object.entries(rawQuery ?? {})) { - queryEntries[key] = { vulnIds: Array.isArray(vulnIds) ? vulnIds : [], cachedAt: new Date(0).toISOString() }; + queryEntries[key] = { + vulnIds: Array.isArray(vulnIds) ? vulnIds : [], + cachedAt: new Date(0).toISOString(), + }; } - const migrated = { version: 3 as const, createdAt: String(parsed.createdAt ?? new Date().toISOString()), entries, queryEntries }; + const migrated = { + version: 3 as const, + createdAt: String(parsed.createdAt ?? new Date().toISOString()), + entries, + queryEntries, + }; debugLog?.("Cache loaded", { path: filePath, queryEntries: Object.keys(migrated.queryEntries).length, @@ -64,7 +92,12 @@ export function loadCache(cacheDirOverride?: string, debugLog?: DebugLogger): Ca // v1 or unknown: no queryEntries if (parsed.version !== 3) { - const upgraded = { version: 3 as const, createdAt: new Date().toISOString(), entries, queryEntries: {} }; + const upgraded = { + version: 3 as const, + createdAt: new Date().toISOString(), + entries, + queryEntries: {}, + }; debugLog?.("Cache loaded", { path: filePath, queryEntries: 0, @@ -77,7 +110,12 @@ export function loadCache(cacheDirOverride?: string, debugLog?: DebugLogger): Ca const rawQuery = parsed.queryEntries as Record | undefined; const queryEntries: CacheFile["queryEntries"] = {}; for (const [key, value] of Object.entries(rawQuery ?? {})) { - if (value && typeof value === "object" && "vulnIds" in value && "cachedAt" in value) { + if ( + value && + typeof value === "object" && + "vulnIds" in value && + "cachedAt" in value + ) { queryEntries[key] = value as QueryCacheEntry; } } @@ -103,7 +141,11 @@ export function loadCache(cacheDirOverride?: string, debugLog?: DebugLogger): Ca } } -export function saveCache(cache: CacheFile, cacheDirOverride?: string, debugLog?: DebugLogger) { +export function saveCache( + cache: CacheFile, + cacheDirOverride?: string, + debugLog?: DebugLogger, +) { const filePath = getCacheFilePath(cacheDirOverride); cache.createdAt = new Date().toISOString(); fs.writeFileSync(filePath, JSON.stringify(cache, null, 2), "utf8"); diff --git a/src/osv/severity.ts b/src/osv/severity.ts index 70a1082a..784ea8b9 100644 --- a/src/osv/severity.ts +++ b/src/osv/severity.ts @@ -53,7 +53,11 @@ function severityFromScore(score: string): SeverityLabel { export function normalizeSeverity(input: string): SeverityLabel { const normalized = String(input || "").toLowerCase(); - if (["none", "low", "medium", "high", "critical", "unknown"].includes(normalized)) { + if ( + ["none", "low", "medium", "high", "critical", "unknown"].includes( + normalized, + ) + ) { return normalized as SeverityLabel; } return "critical"; diff --git a/src/output/cyclonedx.ts b/src/output/cyclonedx.ts index 6e6507dd..e5d88f7c 100644 --- a/src/output/cyclonedx.ts +++ b/src/output/cyclonedx.ts @@ -68,7 +68,7 @@ export function buildCycloneDxBom( version: string, plan: SuggestedFixCommandPlan | null = null, ): CycloneDxBom { - const components: CycloneDxComponent[] = allPackages.map(pkg => { + const components: CycloneDxComponent[] = allPackages.map((pkg) => { const purl = buildPurl(pkg.name, pkg.version); return { type: "library", @@ -80,12 +80,16 @@ export function buildCycloneDxBom( }); // Deduplicate vulnerabilities by CVE ID — one entry per CVE with multiple affects - const vulnMap = new Map(); + const vulnMap = new Map< + string, + { finding: Finding; affects: CycloneDxVulnerabilityAffects[] } + >(); for (const finding of findings) { - const cveIds = finding.cveAliases.length > 0 - ? finding.cveAliases - : finding.vulnerabilities.map(v => v.id); + const cveIds = + finding.cveAliases.length > 0 + ? finding.cveAliases + : finding.vulnerabilities.map((v) => v.id); const purl = buildPurl(finding.pkg.name, finding.pkg.version); @@ -103,7 +107,9 @@ export function buildCycloneDxBom( const vulnerabilities: CycloneDxVulnerability[] = []; for (const [cveId, { finding, affects }] of vulnMap) { - const runnableFixCommand = plan ? findSuggestedCommandForFinding(plan, finding) : null; + const runnableFixCommand = plan + ? findSuggestedCommandForFinding(plan, finding) + : null; const recommendation = runnableFixCommand ?? getRecommendedAction(finding); vulnerabilities.push({ @@ -120,9 +126,7 @@ export function buildCycloneDxBom( const metadata: CycloneDxMetadata = { timestamp: new Date().toISOString(), - tools: [ - { vendor: "OWASP", name: "CVE Lite CLI", version }, - ], + tools: [{ vendor: "OWASP", name: "CVE Lite CLI", version }], }; if (projectMeta && projectMeta.name) { @@ -156,12 +160,25 @@ export function writeCycloneDxReport( const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); const filename = `cve-lite-scan-${ts}.cdx.json`; const outputPath = path.join(process.cwd(), filename); - const bom = buildCycloneDxBom(allPackages, findings, projectMeta, getCliVersion(), plan); + const bom = buildCycloneDxBom( + allPackages, + findings, + projectMeta, + getCliVersion(), + plan, + ); try { fs.writeFileSync(outputPath, JSON.stringify(bom, null, 2)); } catch (err) { - try { fs.rmSync(outputPath, { force: true }); } catch { /* best-effort cleanup */ } - throw new Error(`Failed to write CycloneDX report to ${outputPath}: ${err instanceof Error ? err.message : String(err)}`, { cause: err }); + try { + fs.rmSync(outputPath, { force: true }); + } catch { + /* best-effort cleanup */ + } + throw new Error( + `Failed to write CycloneDX report to ${outputPath}: ${err instanceof Error ? err.message : String(err)}`, + { cause: err }, + ); } return filename; } diff --git a/src/output/debug.ts b/src/output/debug.ts index deb832b7..26ca4ca6 100644 --- a/src/output/debug.ts +++ b/src/output/debug.ts @@ -23,10 +23,12 @@ export function createDebugLogger(enabled: boolean): DebugSession { const log: DebugLogger = (message: string, details?: unknown) => { const time = new Date().toISOString(); - const payload = details === undefined - ? "" - : ` ${formatJson(details)}`; - fs.appendFileSync(outputPath, `${time} [debug] ${message}${payload}\n`, "utf8"); + const payload = details === undefined ? "" : ` ${formatJson(details)}`; + fs.appendFileSync( + outputPath, + `${time} [debug] ${message}${payload}\n`, + "utf8", + ); }; const announcePath = () => { diff --git a/src/output/finding-display.ts b/src/output/finding-display.ts index db4549c7..3f0ede5a 100644 --- a/src/output/finding-display.ts +++ b/src/output/finding-display.ts @@ -2,10 +2,15 @@ import { severityOrder } from "../constants.js"; import { normalizeSeverity } from "../osv/severity.js"; import type { Finding, SeverityLabel } from "../types.js"; -export function selectFindingsForTable(findings: Finding[], minSeverity: string): Finding[] { +export function selectFindingsForTable( + findings: Finding[], + minSeverity: string, +): Finding[] { const normalized = normalizeSeverity(minSeverity) as SeverityLabel; - return findings.filter(finding => - severityOrder[finding.severity] >= severityOrder[normalized] || finding.severity === "unknown" + return findings.filter( + (finding) => + severityOrder[finding.severity] >= severityOrder[normalized] || + finding.severity === "unknown", ); } @@ -15,10 +20,14 @@ export function selectFindingsForCompact( ): Finding[] { const urgentLimit = options?.urgentLimit ?? 3; const urgent = findings - .filter(finding => finding.severity === "critical" || finding.severity === "high") + .filter( + (finding) => + finding.severity === "critical" || finding.severity === "high", + ) .slice(0, urgentLimit); const unknownDirect = findings.filter( - finding => finding.severity === "unknown" && finding.relationship === "direct", + (finding) => + finding.severity === "unknown" && finding.relationship === "direct", ); return mergeUniqueFindings(urgent, unknownDirect); @@ -39,6 +48,9 @@ function mergeUniqueFindings(primary: Finding[], extra: Finding[]): Finding[] { } function findingIdentity(finding: Finding): string { - const vulnIds = finding.vulnerabilities.map(v => v.id).sort().join(","); + const vulnIds = finding.vulnerabilities + .map((v) => v.id) + .sort() + .join(","); return `${finding.pkg.name}@${finding.pkg.version}|${finding.relationship}|${finding.severity}|${vulnIds}`; } diff --git a/src/output/formatters.ts b/src/output/formatters.ts index d8b2ae9e..6b64326b 100644 --- a/src/output/formatters.ts +++ b/src/output/formatters.ts @@ -11,7 +11,10 @@ import { import { loadCache } from "../osv/cache.js"; import { inferSeverity } from "../osv/severity.js"; import { getPrimaryParent } from "../utils/finding.js"; -import { calculatePathCoverage, formatDependencyPath } from "../utils/path-coverage.js"; +import { + calculatePathCoverage, + formatDependencyPath, +} from "../utils/path-coverage.js"; import { pluralize } from "../utils/string.js"; export function formatSeverityLabel(severity: string): string { @@ -30,7 +33,10 @@ export function formatRelationshipLabel(value: string): string { return chalk.gray(value); } -export function formatRelLabel(finding: { relationship: string; pkg: { dev?: boolean } }): string { +export function formatRelLabel(finding: { + relationship: string; + pkg: { dev?: boolean }; +}): string { const base = finding.relationship; return finding.pkg.dev === true ? `${base} · dev` : base; } @@ -46,7 +52,7 @@ export function formatAdvisorySourceLine(sourceLabel: string): string { // MAL-* is the OSV prefix for malicious code advisories function isMaliciousAdvisory(finding: Finding): boolean { - return finding.vulnerabilities.some(v => v.id.startsWith("MAL-")); + return finding.vulnerabilities.some((v) => v.id.startsWith("MAL-")); } export function getRecommendedAction(finding: Finding): string { @@ -63,7 +69,8 @@ export function getRecommendedAction(finding: Finding): string { // available — they can disagree (the hint may not be published on npm or // may itself be vulnerable), and the validated target is what the fix // command actually uses. - const directTarget = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + const directTarget = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; if (finding.relationship === "direct" && directTarget) { return `Upgrade ${finding.pkg.name} to ${directTarget}+ in this project.`; @@ -72,12 +79,16 @@ export function getRecommendedAction(finding: Finding): string { return `No known fix exists for ${finding.pkg.name}. Consider replacing it with an actively maintained alternative.`; } - if (finding.recommendedNpmTransitiveRemediation?.kind === "update-parent-within-range") { + if ( + finding.recommendedNpmTransitiveRemediation?.kind === + "update-parent-within-range" + ) { return `${finding.recommendedNpmTransitiveRemediation.package} already permits ${finding.pkg.name}@${finding.recommendedNpmTransitiveRemediation.targetChildVersion} — run the lockfile refresh command to pick it up.`; } if ( - finding.recommendedNpmTransitiveRemediation?.kind === "upgrade-parent-to-version" && + finding.recommendedNpmTransitiveRemediation?.kind === + "upgrade-parent-to-version" && finding.recommendedNpmTransitiveRemediation.targetVersion ) { const coverage = calculatePathCoverage( @@ -137,44 +148,72 @@ export function summarizeRisk(finding: Finding): string { } let risk = ""; if (finding.severity === "critical" && finding.relationship === "direct") { - risk = "Critical direct dependency. Prioritize this first because the project controls it directly."; + risk = + "Critical direct dependency. Prioritize this first because the project controls it directly."; } else if (finding.severity === "high" && finding.relationship === "direct") { - risk = "High-severity direct dependency. A direct upgrade is likely the fastest path."; - } else if (finding.relationship === "transitive" && finding.recommendedNpmTransitiveRemediation?.kind === "update-parent-within-range") { + risk = + "High-severity direct dependency. A direct upgrade is likely the fastest path."; + } else if ( + finding.relationship === "transitive" && + finding.recommendedNpmTransitiveRemediation?.kind === + "update-parent-within-range" + ) { risk = `Transitive issue. The current parent range can already absorb a safe ${finding.pkg.name} update via ${finding.recommendedNpmTransitiveRemediation.package}.`; - } else if (finding.relationship === "transitive" && finding.recommendedNpmTransitiveRemediation?.kind === "upgrade-parent-to-version") { + } else if ( + finding.relationship === "transitive" && + finding.recommendedNpmTransitiveRemediation?.kind === + "upgrade-parent-to-version" + ) { const coverage = calculatePathCoverage( finding.dependencyPaths, finding.recommendedNpmTransitiveRemediation.viaPath, ); - risk = coverage.coverage === "complete" - ? `Transitive issue. A specific parent upgrade target was found for ${finding.recommendedNpmTransitiveRemediation.package}.` - : `Transitive issue. A path-specific parent upgrade target was found for ${finding.recommendedNpmTransitiveRemediation.package}; run it, rescan, and review remaining paths separately.`; - } else if (finding.relationship === "transitive" && finding.recommendedParentUpgrade) { + risk = + coverage.coverage === "complete" + ? `Transitive issue. A specific parent upgrade target was found for ${finding.recommendedNpmTransitiveRemediation.package}.` + : `Transitive issue. A path-specific parent upgrade target was found for ${finding.recommendedNpmTransitiveRemediation.package}; run it, rescan, and review remaining paths separately.`; + } else if ( + finding.relationship === "transitive" && + finding.recommendedParentUpgrade + ) { const coverage = calculatePathCoverage( finding.dependencyPaths, finding.recommendedParentUpgrade.viaPath, ); - risk = coverage.coverage === "complete" - ? `Transitive issue. A specific parent upgrade target was found for ${finding.recommendedParentUpgrade.package}.` - : `Transitive issue. A path-specific parent upgrade target was found for ${finding.recommendedParentUpgrade.package}; run it, rescan, and review remaining paths separately.`; - } else if (finding.relationship === "transitive" && (finding.validatedFirstFixedVersion ?? finding.firstFixedVersion)) { - const target = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + risk = + coverage.coverage === "complete" + ? `Transitive issue. A specific parent upgrade target was found for ${finding.recommendedParentUpgrade.package}.` + : `Transitive issue. A path-specific parent upgrade target was found for ${finding.recommendedParentUpgrade.package}; run it, rescan, and review remaining paths separately.`; + } else if ( + finding.relationship === "transitive" && + (finding.validatedFirstFixedVersion ?? finding.firstFixedVersion) + ) { + const target = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; risk = `Transitive issue. Look for a parent dependency upgrade that pulls in ${target}+`; } else if (finding.relationship === "transitive") { - risk = "Transitive issue. Review the parent path and check whether an upstream package can be upgraded."; + risk = + "Transitive issue. Review the parent path and check whether an upstream package can be upgraded."; } else if (finding.validatedFirstFixedVersion ?? finding.firstFixedVersion) { - const target = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + const target = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; risk = `A fixed-version hint exists. Aim for at least ${target}.`; } else { - risk = "Review this finding and inspect the dependency path for the safest upgrade path."; + risk = + "Review this finding and inspect the dependency path for the safest upgrade path."; } if (finding.usage && !finding.usage.imported) { if (finding.relationship === "transitive") { - risk = risk.replace("Transitive issue.", "Transitive issue. No direct imports found in source code, so practical reachability may be low."); + risk = risk.replace( + "Transitive issue.", + "Transitive issue. No direct imports found in source code, so practical reachability may be low.", + ); } else if (finding.relationship === "direct") { - risk = risk.replace("direct dependency.", "direct dependency. No direct imports found in source code, so practical reachability may be low."); + risk = risk.replace( + "direct dependency.", + "direct dependency. No direct imports found in source code, so practical reachability may be low.", + ); } } return risk; @@ -186,7 +225,11 @@ function formatParentUpgradeAction( parentName: string, currentVersion: string, targetVersion: string, - coverage: { coverage: "complete" | "partial"; coveredPaths: string[][]; remainingPaths: string[][] }, + coverage: { + coverage: "complete" | "partial"; + coveredPaths: string[][]; + remainingPaths: string[][]; + }, ): string { const pathText = coverage.coveredPaths[0] ? ` for ${formatDependencyPath(coverage.coveredPaths[0])}` @@ -204,7 +247,8 @@ function formatParentUpgradeAction( export function summarizeNextAction(finding: Finding): string { // Prefer the registry-validated target over the raw advisory hint so this // line agrees with the fix-command table. See getRecommendedAction. - const directTarget = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + const directTarget = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; if (isMaliciousAdvisory(finding)) { if (finding.maliciousUnverifiable) { @@ -221,11 +265,15 @@ export function summarizeNextAction(finding: Finding): string { if (finding.relationship === "direct") { return `No known fix exists for ${finding.pkg.name}. Consider replacing it with an actively maintained alternative.`; } - if (finding.recommendedNpmTransitiveRemediation?.kind === "update-parent-within-range") { + if ( + finding.recommendedNpmTransitiveRemediation?.kind === + "update-parent-within-range" + ) { return `Lockfile refresh — ${finding.recommendedNpmTransitiveRemediation.package} already permits a safe version.`; } if ( - finding.recommendedNpmTransitiveRemediation?.kind === "upgrade-parent-to-version" && + finding.recommendedNpmTransitiveRemediation?.kind === + "upgrade-parent-to-version" && finding.recommendedNpmTransitiveRemediation.targetVersion ) { return `Upgrade ${finding.recommendedNpmTransitiveRemediation.package} ${finding.recommendedNpmTransitiveRemediation.currentVersion} -> ${finding.recommendedNpmTransitiveRemediation.targetVersion}.`; @@ -243,7 +291,10 @@ export function summarizeNextAction(finding: Finding): string { return `Inspect the parent dependency chain for ${finding.pkg.name} and choose the safest available upgrade.`; } -export function serializeFinding(finding: Finding, plan?: SuggestedFixCommandPlan | null) { +export function serializeFinding( + finding: Finding, + plan?: SuggestedFixCommandPlan | null, +) { return { package: finding.pkg.name, version: finding.pkg.version, @@ -253,20 +304,25 @@ export function serializeFinding(finding: Finding, plan?: SuggestedFixCommandPla firstFixedVersion: finding.firstFixedVersion, validatedFirstFixedVersion: finding.validatedFirstFixedVersion ?? null, fixVersionValidationNote: finding.fixVersionValidationNote ?? null, - validatedTargetScannedVersions: finding.validatedTargetScannedVersions ?? null, - validatedTargetKnownVulnerableVersions: finding.validatedTargetKnownVulnerableVersions ?? null, + validatedTargetScannedVersions: + finding.validatedTargetScannedVersions ?? null, + validatedTargetKnownVulnerableVersions: + finding.validatedTargetKnownVulnerableVersions ?? null, recommendedAction: getRecommendedAction(finding), - runnableFixCommand: plan ? findSuggestedCommandForFinding(plan, finding) : null, + runnableFixCommand: plan + ? findSuggestedCommandForFinding(plan, finding) + : null, primaryParent: getPrimaryParent(finding), recommendedParentUpgrade: finding.recommendedParentUpgrade, - recommendedNpmTransitiveRemediation: finding.recommendedNpmTransitiveRemediation ?? null, + recommendedNpmTransitiveRemediation: + finding.recommendedNpmTransitiveRemediation ?? null, cves: finding.cveAliases, dependencyPaths: finding.dependencyPaths, usage: finding.usage ?? null, maliciousUnverifiable: finding.maliciousUnverifiable ?? false, maliciousGitSource: finding.maliciousGitSource ?? false, maliciousGitSourcePinned: finding.maliciousGitSourcePinned ?? false, - vulnerabilities: finding.vulnerabilities.map(v => ({ + vulnerabilities: finding.vulnerabilities.map((v) => ({ id: v.id, aliases: v.aliases ?? [], summary: v.summary ?? "", @@ -275,19 +331,28 @@ export function serializeFinding(finding: Finding, plan?: SuggestedFixCommandPla }; } -export function printCacheSummary(cacheDirOverride?: string, options?: { json?: boolean }) { +export function printCacheSummary( + cacheDirOverride?: string, + options?: { json?: boolean }, +) { if (options?.json) return; const cache = loadCache(cacheDirOverride); - const advisoryCount = Object.entries(cache.entries).filter(([, value]) => Boolean(value)).length; - const emptyCount = Object.entries(cache.entries).filter(([, value]) => value === null).length; + const advisoryCount = Object.entries(cache.entries).filter(([, value]) => + Boolean(value), + ).length; + const emptyCount = Object.entries(cache.entries).filter( + ([, value]) => value === null, + ).length; const packageQueryCount = Object.keys(cache.queryEntries).length; const totalCount = advisoryCount + emptyCount; if (totalCount === 0 && packageQueryCount === 0) return; console.log( - chalk.gray(`Cache: ${packageQueryCount} package match ${pluralize(packageQueryCount, "record")}, ${advisoryCount} advisory detail ${pluralize(advisoryCount, "record")}`) + + chalk.gray( + `Cache: ${packageQueryCount} package match ${pluralize(packageQueryCount, "record")}, ${advisoryCount} advisory detail ${pluralize(advisoryCount, "record")}`, + ) + (emptyCount > 0 ? chalk.gray(`, ${emptyCount} empty ${pluralize(emptyCount, "lookup")}`) : ""), @@ -309,11 +374,11 @@ export function sortFindingsForOutput(findings: Finding[]): Finding[] { const sevDelta = severityOrder[b.severity] - severityOrder[a.severity]; if (sevDelta !== 0) return sevDelta; - const usageScore = (f: Finding) => f.usage?.imported ? 1 : 0; + const usageScore = (f: Finding) => (f.usage?.imported ? 1 : 0); const usageDelta = usageScore(b) - usageScore(a); if (usageDelta !== 0) return usageDelta; - const relScore = (f: Finding) => f.relationship === "direct" ? 1 : 0; + const relScore = (f: Finding) => (f.relationship === "direct" ? 1 : 0); const relDelta = relScore(b) - relScore(a); if (relDelta !== 0) return relDelta; @@ -322,5 +387,6 @@ export function sortFindingsForOutput(findings: Finding[]): Finding[] { } export function countUniqueAdvisories(findings: Finding[]): number { - return new Set(findings.flatMap(f => f.vulnerabilities.map(v => v.id))).size; + return new Set(findings.flatMap((f) => f.vulnerabilities.map((v) => v.id))) + .size; } diff --git a/src/output/html-reporter.ts b/src/output/html-reporter.ts index c6b56d9b..772f1e88 100644 --- a/src/output/html-reporter.ts +++ b/src/output/html-reporter.ts @@ -1,12 +1,19 @@ import fs from "node:fs"; import path from "node:path"; import { spawn } from "node:child_process"; -import { serializeFinding, summarizeNextAction, summarizeRisk } from "./formatters.js"; +import { + serializeFinding, + summarizeNextAction, + summarizeRisk, +} from "./formatters.js"; import { LOGO_BASE64 } from "./logo-base64.js"; import { OWASP_LOGO_BASE64 } from "./owasp-logo-base64.js"; import { isMajorVersionBump } from "../utils/version.js"; import { pluralize } from "../utils/string.js"; -import { MAL_GIT_SOURCE_PINNED_DISPLAY, MAL_GIT_SOURCE_FLOATING_DISPLAY } from "../constants.js"; +import { + MAL_GIT_SOURCE_PINNED_DISPLAY, + MAL_GIT_SOURCE_FLOATING_DISPLAY, +} from "../constants.js"; import type { Finding } from "../types.js"; import type { SuggestedFixCommandPlan } from "../remediation/fix-commands.js"; @@ -54,7 +61,7 @@ export function buildReportData(params: { packageManager: params.packageManager, lockfileSource: params.lockfileSource, packageCount: params.packageCount, - findings: params.findings.map(finding => + findings: params.findings.map((finding) => serializeHtmlFinding(finding, params.suggestedFixCommands), ), suggestedFixCommands: params.suggestedFixCommands, @@ -195,25 +202,37 @@ button.header-link:hover{color:#58a6ff;border-color:#58a6ff} .tier-err{display:inline-flex;align-items:center;gap:4px;font-size:10px;padding:2px 8px;border-radius:4px;background:#ff000011;color:#ff7b72;border:1px solid #ff7b7222;margin-bottom:6px}`; export function renderHtmlReport(data: ReportData): string { - const projectName = path.basename(data.projectPath); const scanDate = new Date(data.scannedAt).toLocaleString(); - const counts = { critical: 0, high: 0, medium: 0, low: 0, unknown: 0, none: 0 }; + const counts = { + critical: 0, + high: 0, + medium: 0, + low: 0, + unknown: 0, + none: 0, + }; for (const f of data.findings) { const sev = f.severity as keyof typeof counts; if (sev in counts) counts[sev]++; } - const totalCVEs = new Set(data.findings.flatMap(f => f.vulnerabilities.map(v => v.id))).size; + const totalCVEs = new Set( + data.findings.flatMap((f) => f.vulnerabilities.map((v) => v.id)), + ).size; const noticesHtml = renderNotices(data.notes, data.warnings); const fixPlanHtml = renderFixPlan(data.suggestedFixCommands); const skippedKeys = new Set( - (data.suggestedFixCommands?.skipped ?? []).map(s => `${s.package}@${s.version}`) + (data.suggestedFixCommands?.skipped ?? []).map( + (s) => `${s.package}@${s.version}`, + ), ); const skippedSectionHtml = renderSkippedSection(data.suggestedFixCommands); - const findingRowsHtml = data.findings.map((f, i) => renderFindingRow(f, i, skippedKeys)).join("\n"); + const findingRowsHtml = data.findings + .map((f, i) => renderFindingRow(f, i, skippedKeys)) + .join("\n"); const dataJson = JSON.stringify(data) .replace(//g, "\\u003e") @@ -458,34 +477,48 @@ function renderRelBadge(finding: SerializedFinding): string { return `${escapeHtml(finding.relationship)}`; } -export function renderFindingRow(finding: SerializedFinding, idx: number, skippedKeys?: ReadonlySet): string { - const cveLinks = finding.cves.length > 0 - ? finding.cves.map(advisoryLink).join(", ") - : finding.vulnerabilities.map(v => advisoryLink(v.id)).join(", "); - - const isMalicious = finding.vulnerabilities.some(v => v.id.startsWith("MAL-")); +export function renderFindingRow( + finding: SerializedFinding, + idx: number, + skippedKeys?: ReadonlySet, +): string { + const cveLinks = + finding.cves.length > 0 + ? finding.cves.map(advisoryLink).join(", ") + : finding.vulnerabilities.map((v) => advisoryLink(v.id)).join(", "); + + const isMalicious = finding.vulnerabilities.some((v) => + v.id.startsWith("MAL-"), + ); const isSkipped = skippedKeys?.has(`${finding.package}@${finding.version}`); - const fixVersion = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; - const fixHtml = isSkipped && fixVersion - ? `${escapeHtml(fixVersion)} ⊘` - : fixVersion - ? `${escapeHtml(fixVersion)}` - : isMalicious && finding.maliciousUnverifiable - ? `Unverifiable (private source)` - : isMalicious && finding.maliciousGitSource && finding.maliciousGitSourcePinned - ? `${escapeHtml(MAL_GIT_SOURCE_PINNED_DISPLAY)}` - : isMalicious && finding.maliciousGitSource - ? `${escapeHtml(MAL_GIT_SOURCE_FLOATING_DISPLAY)}` - : isMalicious - ? `⚠ Malicious` - : `⚠ No fix`; - - const depPathHtml = finding.dependencyPaths.length > 0 - ? finding.dependencyPaths[0].map((node, i, arr) => { - const isLast = i === arr.length - 1; - return `${escapeHtml(node)}${isLast ? "" : ''}`; - }).join("") - : `${escapeHtml(finding.package)}`; + const fixVersion = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + const fixHtml = + isSkipped && fixVersion + ? `${escapeHtml(fixVersion)} ⊘` + : fixVersion + ? `${escapeHtml(fixVersion)}` + : isMalicious && finding.maliciousUnverifiable + ? `Unverifiable (private source)` + : isMalicious && + finding.maliciousGitSource && + finding.maliciousGitSourcePinned + ? `${escapeHtml(MAL_GIT_SOURCE_PINNED_DISPLAY)}` + : isMalicious && finding.maliciousGitSource + ? `${escapeHtml(MAL_GIT_SOURCE_FLOATING_DISPLAY)}` + : isMalicious + ? `⚠ Malicious` + : `⚠ No fix`; + + const depPathHtml = + finding.dependencyPaths.length > 0 + ? finding.dependencyPaths[0] + .map((node, i, arr) => { + const isLast = i === arr.length - 1; + return `${escapeHtml(node)}${isLast ? "" : ''}`; + }) + .join("") + : `${escapeHtml(finding.package)}`; const description = finding.vulnerabilities[0]?.summary ?? ""; const runnable = finding.runnableFixCommand ?? null; @@ -496,11 +529,12 @@ export function renderFindingRow(finding: SerializedFinding, idx: number, skippe
` : `

${escapeHtml(finding.recommendedAction ?? "")}

`; - const contextColHtml = finding.relationship === "transitive" - ? renderTransitiveContextCol(finding) - : ""; + const contextColHtml = + finding.relationship === "transitive" + ? renderTransitiveContextCol(finding) + : ""; - return ` + return `
${escapeHtml(finding.package)}
${escapeHtml(finding.version)}
${fixHtml} @@ -569,8 +603,8 @@ function renderNotices(notes: string[], warnings: string[]): string { if (notes.length === 0 && warnings.length === 0) return ""; const total = notes.length + warnings.length; const items = [ - ...warnings.map(w => `
  • ${escapeHtml(w)}
  • `), - ...notes.map(n => `
  • ${escapeHtml(n)}
  • `), + ...warnings.map((w) => `
  • ${escapeHtml(w)}
  • `), + ...notes.map((n) => `
  • ${escapeHtml(n)}
  • `), ].join("\n"); return `
    @@ -578,15 +612,21 @@ function renderNotices(notes: string[], warnings: string[]): string {
    `; } -export function renderSkippedSection(plan: SuggestedFixCommandPlan | null): string { +export function renderSkippedSection( + plan: SuggestedFixCommandPlan | null, +): string { if (!plan || plan.skipped.length === 0) return ""; - const skippedRows = plan.skipped.map(s => ` + const skippedRows = plan.skipped + .map( + (s) => `
    ${escapeHtml(s.package)}@${escapeHtml(s.version)} ${escapeHtml(s.relationship)} ${escapeHtml(s.reason)} -
    `).join("\n"); +
    `, + ) + .join("\n"); return `
    @@ -603,58 +643,76 @@ export function renderSkippedSection(plan: SuggestedFixCommandPlan | null): stri export function renderFixPlan(plan: SuggestedFixCommandPlan | null): string { if (!plan || plan.sections.length === 0) return ""; - const directCount = plan.targets.filter(t => t.kind === "direct").length; + const directCount = plan.targets.filter((t) => t.kind === "direct").length; const skippedCount = plan.skipped.length; - const commandRows = plan.sections.map(section => { - const targetRows = section.targets.map(t => { - const displayVersion = t.displayTargetVersion ?? t.targetVersion; - const isMajor = t.currentVersion ? isMajorVersionBump(t.currentVersion, t.targetVersion) : false; - const breakBadge = isMajor ? ` ⚠ breaking` : ""; - const versionHtml = t.currentVersion - ? `${escapeHtml(t.package)}@${escapeHtml(t.currentVersion)}${escapeHtml(displayVersion)}` - : `${escapeHtml(t.package)}${escapeHtml(displayVersion)}`; - const statsHtml = t.scannedVersions != null - ? ` ${t.scannedVersions} scanned${t.knownVulnerableVersions != null ? ` · ${t.knownVulnerableVersions} still vulnerable` : ""}` - : ""; - const noteHtml = t.adjusted && t.adjustmentNote - ? ` ${escapeHtml(t.adjustmentNote)}` - : ""; - const coverageHtml = t.coverage === "partial" - ? ` Path-specific remediation. Run this command, then rescan; ${t.remainingPaths?.length ?? 0} other known ${pluralize(t.remainingPaths?.length ?? 0, "path")} may still need separate parent upgrades.` - : ""; - const chainProofHtml = t.chainProof && t.chainSafeVersion - ? (() => { - const hops = t.chainProof.map(h => `${escapeHtml(h.name)}@${escapeHtml(h.version)}`); - const safe = escapeHtml(t.chainSafeVersion); - const proofText = hops.length > 0 - ? `resolves via ${hops.join(" -> ")} -> ${safe} (safe)` - : `resolves to ${safe} (safe)`; - return ` ${proofText}`; - })() - : ""; - return `
    ${versionHtml}${breakBadge}${statsHtml}${noteHtml}${coverageHtml}${chainProofHtml}
    `; - }).join("\n"); - - const targetsHtml = section.targets.length > 0 - ? `
    ${targetRows}
    ` - : ""; - - return ` + const commandRows = plan.sections + .map((section) => { + const targetRows = section.targets + .map((t) => { + const displayVersion = t.displayTargetVersion ?? t.targetVersion; + const isMajor = t.currentVersion + ? isMajorVersionBump(t.currentVersion, t.targetVersion) + : false; + const breakBadge = isMajor + ? ` ⚠ breaking` + : ""; + const versionHtml = t.currentVersion + ? `${escapeHtml(t.package)}@${escapeHtml(t.currentVersion)}${escapeHtml(displayVersion)}` + : `${escapeHtml(t.package)}${escapeHtml(displayVersion)}`; + const statsHtml = + t.scannedVersions != null + ? ` ${t.scannedVersions} scanned${t.knownVulnerableVersions != null ? ` · ${t.knownVulnerableVersions} still vulnerable` : ""}` + : ""; + const noteHtml = + t.adjusted && t.adjustmentNote + ? ` ${escapeHtml(t.adjustmentNote)}` + : ""; + const coverageHtml = + t.coverage === "partial" + ? ` Path-specific remediation. Run this command, then rescan; ${t.remainingPaths?.length ?? 0} other known ${pluralize(t.remainingPaths?.length ?? 0, "path")} may still need separate parent upgrades.` + : ""; + const chainProofHtml = + t.chainProof && t.chainSafeVersion + ? (() => { + const hops = t.chainProof.map( + (h) => `${escapeHtml(h.name)}@${escapeHtml(h.version)}`, + ); + const safe = escapeHtml(t.chainSafeVersion); + const proofText = + hops.length > 0 + ? `resolves via ${hops.join(" -> ")} -> ${safe} (safe)` + : `resolves to ${safe} (safe)`; + return ` ${proofText}`; + })() + : ""; + return `
    ${versionHtml}${breakBadge}${statsHtml}${noteHtml}${coverageHtml}${chainProofHtml}
    `; + }) + .join("\n"); + + const targetsHtml = + section.targets.length > 0 + ? `
    ${targetRows}
    ` + : ""; + + return `
    ${escapeHtml(section.command)} ${escapeHtml(section.title)}
    ${targetsHtml}`; - }).join("\n"); + }) + .join("\n"); - const skippedBadgePart = skippedCount > 0 - ? ` · ${skippedCount} skipped ⊘` - : ""; + const skippedBadgePart = + skippedCount > 0 + ? ` · ${skippedCount} skipped ⊘` + : ""; - const coverageText = plan.coveredFindingCount === plan.totalFindingCount - ? `Running all commands should fix all ${plan.totalFindingCount} findings.` - : `Running all commands should fix ${plan.coveredFindingCount} of ${plan.totalFindingCount} findings.`; + const coverageText = + plan.coveredFindingCount === plan.totalFindingCount + ? `Running all commands should fix all ${plan.totalFindingCount} findings.` + : `Running all commands should fix ${plan.coveredFindingCount} of ${plan.totalFindingCount} findings.`; return `
    @@ -676,7 +734,10 @@ export async function writeHtmlReport(params: { try { fs.mkdirSync(params.outputDir, { recursive: true }); } catch (err) { - throw new Error(`Failed to create report directory ${params.outputDir}: ${err instanceof Error ? err.message : String(err)}`, { cause: err }); + throw new Error( + `Failed to create report directory ${params.outputDir}: ${err instanceof Error ? err.message : String(err)}`, + { cause: err }, + ); } const indexPath = path.join(params.outputDir, "index.html"); @@ -687,9 +748,16 @@ export async function writeHtmlReport(params: { fs.writeFileSync(indexPath, renderHtmlReport(params.data), "utf8"); } catch (err) { if (!dirExisted) { - try { fs.rmSync(params.outputDir, { recursive: true, force: true }); } catch { /* best-effort cleanup */ } + try { + fs.rmSync(params.outputDir, { recursive: true, force: true }); + } catch { + /* best-effort cleanup */ + } } - throw new Error(`Failed to write HTML report to ${params.outputDir}: ${err instanceof Error ? err.message : String(err)}`, { cause: err }); + throw new Error( + `Failed to write HTML report to ${params.outputDir}: ${err instanceof Error ? err.message : String(err)}`, + { cause: err }, + ); } if (params.autoOpen) { @@ -702,18 +770,22 @@ export async function writeHtmlReport(params: { export function openInBrowser(filePath: string): void { if (!path.isAbsolute(filePath)) return; - const cmd = process.platform === "darwin" - ? "open" - : process.platform === "win32" - ? "cmd" - : "xdg-open"; + const cmd = + process.platform === "darwin" + ? "open" + : process.platform === "win32" + ? "cmd" + : "xdg-open"; - const args = process.platform === "win32" - ? ["/c", "start", "", filePath] - : [filePath]; + const args = + process.platform === "win32" ? ["/c", "start", "", filePath] : [filePath]; try { - const child = spawn(cmd, args, { stdio: "ignore", detached: true, shell: false }); + const child = spawn(cmd, args, { + stdio: "ignore", + detached: true, + shell: false, + }); child.unref(); } catch { // Best-effort only: report creation should not fail if the OS open command is unavailable. diff --git a/src/output/multi-folder-html-reporter.ts b/src/output/multi-folder-html-reporter.ts index e576c317..662deef4 100644 --- a/src/output/multi-folder-html-reporter.ts +++ b/src/output/multi-folder-html-reporter.ts @@ -18,7 +18,14 @@ import type { SeverityLabel } from "../types.js"; type SevCounts = Record; function summaryCounts(results: MultiFolderScanResult[]): SevCounts { - const counts: SevCounts = { critical: 0, high: 0, medium: 0, low: 0, unknown: 0, none: 0 }; + const counts: SevCounts = { + critical: 0, + high: 0, + medium: 0, + low: 0, + unknown: 0, + none: 0, + }; for (const r of results) { for (const f of r.sorted) { const sev = f.severity as keyof SevCounts; @@ -30,7 +37,7 @@ function summaryCounts(results: MultiFolderScanResult[]): SevCounts { function renderFolderNotices(coverage: string[]): string { if (coverage.length === 0) return ""; - const items = coverage.map(n => `
  • ${escapeHtml(n)}
  • `).join("\n"); + const items = coverage.map((n) => `
  • ${escapeHtml(n)}
  • `).join("\n"); return `
      ${items}
    @@ -49,21 +56,26 @@ function renderFolderSection( if (sev in counts) counts[sev]++; } - const severityParts = (["critical", "high", "medium", "low", "unknown"] as const) - .filter(s => counts[s] > 0) - .map(s => `${counts[s]} ${s}`) + const severityParts = ( + ["critical", "high", "medium", "low", "unknown"] as const + ) + .filter((s) => counts[s] > 0) + .map((s) => `${counts[s]} ${s}`) .join(", "); const summaryText = severityParts || "no findings"; const isOpen = counts.critical > 0; const fi = folderIdx; - const findingRowsHtml = serialized.map((f, i) => renderFindingRow(f, idxOffset + i)).join("\n"); + const findingRowsHtml = serialized + .map((f, i) => renderFindingRow(f, idxOffset + i)) + .join("\n"); const fixPlanHtml = renderFixPlan(result.suggestedFixCommands); const noticesHtml = renderFolderNotices(result.coverage); - const emptyRow = result.sorted.length === 0 - ? `No findings` - : ""; + const emptyRow = + result.sorted.length === 0 + ? `No findings` + : ""; return `
    @@ -121,7 +133,10 @@ export async function writeMultiFolderHtmlReport(params: { const projectName = path.basename(params.projectPath); const scanDate = new Date().toLocaleString(); const counts = summaryCounts(params.results); - const totalFindings = params.results.reduce((sum, r) => sum + r.sorted.length, 0); + const totalFindings = params.results.reduce( + (sum, r) => sum + r.sorted.length, + 0, + ); const totalCVEs = params.results.reduce( (sum, r) => sum + r.sorted.reduce((s, f) => s + f.cveAliases.length, 0), 0, @@ -129,20 +144,20 @@ export async function writeMultiFolderHtmlReport(params: { const folderCount = params.results.length; // Pre-serialize findings once per folder for both HTML rendering and JS filter/sort - const allSerialized: SerializedFinding[][] = params.results.map(r => - r.sorted.map(f => serializeHtmlFinding(f, r.suggestedFixCommands)), + const allSerialized: SerializedFinding[][] = params.results.map((r) => + r.sorted.map((f) => serializeHtmlFinding(f, r.suggestedFixCommands)), ); // Compact per-folder findings for JavaScript (only fields needed for filter/sort/search) const folderFindingsJson = JSON.stringify( - allSerialized.map(findings => - findings.map(f => ({ + allSerialized.map((findings) => + findings.map((f) => ({ package: f.package, version: f.version, severity: f.severity, relationship: f.relationship, cves: f.cves, - vulnerabilities: f.vulnerabilities.map(v => ({ id: v.id })), + vulnerabilities: f.vulnerabilities.map((v) => ({ id: v.id })), })), ), ) diff --git a/src/output/multi-folder-printer.ts b/src/output/multi-folder-printer.ts index b7964707..1ccf3114 100644 --- a/src/output/multi-folder-printer.ts +++ b/src/output/multi-folder-printer.ts @@ -36,22 +36,38 @@ export function printMultiFolderResults( if (options.verbose) { printSummary(result.sorted, result.allPackages.length, result.scanInput); printActionSummary(result.sorted); - printSuggestedFixCommands(result.sorted, result.scanInput, { offline, subfolder: result.subfolder }); - printSuggestedFixCommandSkips(result.sorted, result.scanInput, { offline, subfolder: result.subfolder }); + printSuggestedFixCommands(result.sorted, result.scanInput, { + offline, + subfolder: result.subfolder, + }); + printSuggestedFixCommandSkips(result.sorted, result.scanInput, { + offline, + subfolder: result.subfolder, + }); if (result.scanInput.skippedDependencies.length > 0) { printSkippedDependencies(result.scanInput.skippedDependencies); } if (result.sorted.length > 0) { if (result.tableFindings.length > 0) { - printTable(result.tableFindings, options.all ? null : result.minSeverity); + printTable( + result.tableFindings, + options.all ? null : result.minSeverity, + ); } else { - logInfo(`No findings met the table threshold of ${result.minSeverity}. Re-run with --all to show everything.`, options); + logInfo( + `No findings met the table threshold of ${result.minSeverity}. Re-run with --all to show everything.`, + options, + ); } } printCoverage([...result.scanInput.notes, ...result.coverage]); printFinalStatus(result.sorted); } else { - printCompactOutput(result.sorted, result.scanInput, { offline, all: !!options.all, subfolder: result.subfolder }); + printCompactOutput(result.sorted, result.scanInput, { + offline, + all: !!options.all, + subfolder: result.subfolder, + }); } if (i < results.length - 1) { @@ -60,7 +76,15 @@ export function printMultiFolderResults( } const totalFindings = results.reduce((sum, r) => sum + r.sorted.length, 0); - const folderNames = results.map(r => `${r.subfolder}/`).join(", "); - console.log(chalk.gray(`\nScanned ${results.length} ${pluralize(results.length, "folder")}: ${folderNames}`)); - console.log(chalk.gray(`${totalFindings} total ${pluralize(totalFindings, "finding")} across all folders`)); + const folderNames = results.map((r) => `${r.subfolder}/`).join(", "); + console.log( + chalk.gray( + `\nScanned ${results.length} ${pluralize(results.length, "folder")}: ${folderNames}`, + ), + ); + console.log( + chalk.gray( + `${totalFindings} total ${pluralize(totalFindings, "finding")} across all folders`, + ), + ); } diff --git a/src/output/owasp-logo-base64.ts b/src/output/owasp-logo-base64.ts index 43673167..5c590335 100644 --- a/src/output/owasp-logo-base64.ts +++ b/src/output/owasp-logo-base64.ts @@ -1 +1,2 @@ -export const OWASP_LOGO_BASE64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAHTkAAA0aCAYAAAAJlRwRAAAACXBIWXMAAC4jAAAuIwF4pT92AAAgAElEQVR4nOzdMQEAIAzAMMC/5yFjRxMFddA7MwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6HrbAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADALpNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAPnt3IAAAAAAgyN96kEskAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAKy8yBUAACAASURBVAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAQe3cgAAAAACDI33qQSyQAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAABB7dyAAAAAAIMjfepBLJAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAWm3U1gAAIABJREFUAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEHt3IAAAAAAgyN96kEskAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAIDYu6PcJoIACoKJhBDc/7AgfsIHEkFYdpy1d3dmuuoE7wSvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAA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c5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEHt3IAAAAAAgyN96kEskAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAADkqQqgAAAgAElEQVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFIDYuwMBAAAAAEH+1oNcIgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAgNi7AwEAAAAAQf7Wg1wiAQAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAACofbNQAACAASURBVAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAACA2LsDAQAAAABB/taDXCIBAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAIDauwMBAAAAAEH+1oNcIgEAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAEVIeCgAAAkVJREFUAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAXBrm8LJ9h3zxAAAAAElFTkSuQmCC"; +export const OWASP_LOGO_BASE64 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAHTkAAA0aCAYAAAAJlRwRAAAACXBIWXMAAC4jAAAuIwF4pT92AAAgAElEQVR4nOzdMQEAIAzAMMC/5yFjRxMFddA7MwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6HrbAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADALpNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOJNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgzOQUAAAAAPnt3IAAAAAAgyN96kEskAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAKy8yBUAACAASURBVAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAQe3cgAAAAACDI33qQSyQAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAABB7dyAAAAAAIMjfepBLJAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAWm3U1gAAIABJREFUAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEHt3IAAAAAAgyN96kEskAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAIDYu6PcJoIACoKJhBDc/7AgfsIHEkFYdpy1d3dmuuoE7wSvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiBM5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA4kVMAAAAAAAAA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c5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEHt3IAAAAAAgyN96kEskAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAADkqQqgAAAgAElEQVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFIDYuwMBAAAAAEH+1oNcIgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAgNi7AwEAAAAAQf7Wg1wiAQAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAACofbNQAACAASURBVAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAACA2LsDAQAAAABB/taDXCIBAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAIDauwMBAAAAAEH+1oNcIgEAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOYkpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDnJKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMSU4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYE5yCgAAAAAAAAAAAAAAAAAAAAAAEVIeCgAAAkVJREFUAAAAAAAAAAAAAABzklMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJOcAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5iSnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnOQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOckpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxJTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgTnIKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHOSUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYk5wCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJzkFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmJKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCc5BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ySkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzElOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBOcgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc5JTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiTnAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAXBrm8LJ9h3zxAAAAAElFTkSuQmCC"; diff --git a/src/output/printers.ts b/src/output/printers.ts index d82f7c33..04d38e90 100644 --- a/src/output/printers.ts +++ b/src/output/printers.ts @@ -9,7 +9,7 @@ import { formatSeverityLabel, formatRelationshipLabel, formatRelLabel, - sortFindingsForOutput + sortFindingsForOutput, } from "./formatters.js"; import { pluralize } from "../utils/string.js"; import { selectFindingsForCompact } from "./finding-display.js"; @@ -24,12 +24,24 @@ import { MAL_GIT_SOURCE_FLOATING_DISPLAY, } from "../constants.js"; -export function printSummary(findings: Finding[], packageCount: number, scanInput: ScanInput) { +export function printSummary( + findings: Finding[], + packageCount: number, + scanInput: ScanInput, +) { if (findings.length === 0) { if (scanInput.mode === "manifest-fallback") { - console.log(chalk.greenBright(`✓ No known OSV matches found for manifest fallback packages (${packageCount} exact direct dependencies checked)`)); + console.log( + chalk.greenBright( + `✓ No known OSV matches found for manifest fallback packages (${packageCount} exact direct dependencies checked)`, + ), + ); } else { - console.log(chalk.greenBright(`✓ No known OSV vulnerability matches found in parsed lockfile packages (${packageCount} checked)`)); + console.log( + chalk.greenBright( + `✓ No known OSV vulnerability matches found in parsed lockfile packages (${packageCount} checked)`, + ), + ); } return; } @@ -39,34 +51,50 @@ export function printSummary(findings: Finding[], packageCount: number, scanInpu const cveLabel = totalCVEs === 1 ? "CVE" : "CVEs"; const counts = { - critical: findings.filter(f => f.severity === "critical").length, - high: findings.filter(f => f.severity === "high").length, - medium: findings.filter(f => f.severity === "medium").length, - low: findings.filter(f => f.severity === "low").length, - unknown: findings.filter(f => f.severity === "unknown").length + critical: findings.filter((f) => f.severity === "critical").length, + high: findings.filter((f) => f.severity === "high").length, + medium: findings.filter((f) => f.severity === "medium").length, + low: findings.filter((f) => f.severity === "low").length, + unknown: findings.filter((f) => f.severity === "unknown").length, }; - console.log(chalk.redBright(`✗ Found ${findings.length} ${pkgLabel} (${totalCVEs} ${cveLabel}) with known OSV matches from ${scanInput.source}`)); + console.log( + chalk.redBright( + `✗ Found ${findings.length} ${pkgLabel} (${totalCVEs} ${cveLabel}) with known OSV matches from ${scanInput.source}`, + ), + ); console.log(renderSeverityTable(counts)); } export function printActionSummary(findings: Finding[]) { if (findings.length === 0) return; - const direct = findings.filter(f => f.relationship === "direct").length; - const transitive = findings.filter(f => f.relationship === "transitive").length; - const unknown = findings.filter(f => f.relationship === "unknown").length; + const direct = findings.filter((f) => f.relationship === "direct").length; + const transitive = findings.filter( + (f) => f.relationship === "transitive", + ).length; + const unknown = findings.filter((f) => f.relationship === "unknown").length; const uniqueAdvisories = countUniqueAdvisories(findings); - const fixable = findings.filter(f => Boolean(f.firstFixedVersion)).length; + const fixable = findings.filter((f) => Boolean(f.firstFixedVersion)).length; console.log(""); console.log(chalk.bold.cyan("Quick take")); - console.log(`- ${chalk.green(String(direct))} vulnerable ${pluralize(direct, "package")} look directly fixable in this project.`); - console.log(`- ${chalk.yellow(String(transitive))} ${pluralize(transitive, "issue")} come through other dependencies.`); + console.log( + `- ${chalk.green(String(direct))} vulnerable ${pluralize(direct, "package")} look directly fixable in this project.`, + ); + console.log( + `- ${chalk.yellow(String(transitive))} ${pluralize(transitive, "issue")} come through other dependencies.`, + ); if (unknown > 0) { - console.log(`- ${chalk.magenta(String(unknown))} ${pluralize(unknown, "package")} could not be clearly classified as direct or transitive.`); + console.log( + `- ${chalk.magenta(String(unknown))} ${pluralize(unknown, "package")} could not be clearly classified as direct or transitive.`, + ); } - console.log(`- ${chalk.blueBright(String(uniqueAdvisories))} ${pluralize(uniqueAdvisories, "CVE")} matched overall.`); - console.log(`- ${chalk.blue(String(fixable))} ${pluralize(fixable, "package")} include a fixed-version hint from OSV.`); + console.log( + `- ${chalk.blueBright(String(uniqueAdvisories))} ${pluralize(uniqueAdvisories, "CVE")} matched overall.`, + ); + console.log( + `- ${chalk.blue(String(fixable))} ${pluralize(fixable, "package")} include a fixed-version hint from OSV.`, + ); } function printChainProofLines(targets: SuggestedFixTarget[]): void { @@ -75,10 +103,11 @@ function printChainProofLines(targets: SuggestedFixTarget[]): void { const pkgLabel = target.chainVulnerablePackage ? `${target.chainVulnerablePackage}@${target.chainSafeVersion}` : target.chainSafeVersion; - const hops = target.chainProof.map(h => `${h.name}@${h.version}`); - const proof = hops.length > 0 - ? ` resolves via ${hops.join(" -> ")} -> ${pkgLabel} (safe)` - : ` resolves to ${pkgLabel} (safe)`; + const hops = target.chainProof.map((h) => `${h.name}@${h.version}`); + const proof = + hops.length > 0 + ? ` resolves via ${hops.join(" -> ")} -> ${pkgLabel} (safe)` + : ` resolves to ${pkgLabel} (safe)`; console.log(chalk.gray(proof)); } } @@ -92,11 +121,15 @@ export function printSuggestedFixCommands( if (!plan) return; if (plan.sections.length === 0) return; const sharedDirectTableWidths = computeSharedDirectTableWidths(plan.sections); - const sharedParentUpgradeTableWidths = computeSharedParentUpgradeTableWidths(plan.sections); + const sharedParentUpgradeTableWidths = computeSharedParentUpgradeTableWidths( + plan.sections, + ); console.log(""); console.log(chalk.bold.yellow("🛠 Suggested Fix Commands")); - console.log(`${chalk.gray("Detected package manager:")} ${chalk.cyan(plan.packageManager)} ${chalk.gray(`(${plan.sourceLabel})`)}`); + console.log( + `${chalk.gray("Detected package manager:")} ${chalk.cyan(plan.packageManager)} ${chalk.gray(`(${plan.sourceLabel})`)}`, + ); console.log(chalk.white(formatFixCommandSummary(plan))); for (const section of plan.sections) { @@ -106,19 +139,31 @@ export function printSuggestedFixCommands( if (section.kind === "direct" || section.kind === "direct-adjusted") { const validationSummary = summarizeAdjustedValidation(section.targets); const remainingNotes: string[] = []; - printDirectTargetsTable(section.targets, remainingNotes, validationSummary, sharedDirectTableWidths); + printDirectTargetsTable( + section.targets, + remainingNotes, + validationSummary, + sharedDirectTableWidths, + ); for (const note of remainingNotes) { console.log(chalk.gray(` Note: ${note}`)); } } else if (section.kind === "urgent") { - const directTargets = section.targets.filter(t => t.kind === "direct"); - const parentUpgradeTargets = section.targets.filter(t => t.kind === "parent-upgrade" || t.kind === "parent-update"); + const directTargets = section.targets.filter((t) => t.kind === "direct"); + const parentUpgradeTargets = section.targets.filter( + (t) => t.kind === "parent-upgrade" || t.kind === "parent-update", + ); if (directTargets.length > 0) { const validationSummary = summarizeAdjustedValidation(directTargets); const remainingNotes: string[] = []; // Urgent sections compute their own widths so the Breaking? column // is not truncated by widths derived from smaller non-urgent sections. - printDirectTargetsTable(directTargets, remainingNotes, validationSummary, undefined); + printDirectTargetsTable( + directTargets, + remainingNotes, + validationSummary, + undefined, + ); for (const note of remainingNotes) { console.log(chalk.gray(` Note: ${note}`)); } @@ -127,7 +172,10 @@ export function printSuggestedFixCommands( printParentUpgradeTargetsTable(parentUpgradeTargets, undefined); } } else if (shouldRenderParentUpgradeTable(section.targets)) { - printParentUpgradeTargetsTable(section.targets, sharedParentUpgradeTableWidths); + printParentUpgradeTargetsTable( + section.targets, + sharedParentUpgradeTableWidths, + ); } console.log(renderCommandCallout(section.command)); @@ -136,9 +184,14 @@ export function printSuggestedFixCommands( if (plan.coveredFindingCount > 0) { console.log(""); - const coverage = plan.coveredFindingCount === plan.totalFindingCount - ? chalk.gray(`Running all commands above should fix all ${plan.totalFindingCount} findings.`) - : chalk.gray(`Running all commands above should fix ${chalk.white(String(plan.coveredFindingCount))} of ${chalk.white(String(plan.totalFindingCount))} findings.`); + const coverage = + plan.coveredFindingCount === plan.totalFindingCount + ? chalk.gray( + `Running all commands above should fix all ${plan.totalFindingCount} findings.`, + ) + : chalk.gray( + `Running all commands above should fix ${chalk.white(String(plan.coveredFindingCount))} of ${chalk.white(String(plan.totalFindingCount))} findings.`, + ); console.log(coverage); } } @@ -151,12 +204,16 @@ export function printSuggestedFixCommandSkips( const plan = buildSuggestedFixCommandPlan(findings, scanInput, options); if (!plan || plan.skipped.length === 0) return; - const unpublishable = plan.skipped.filter(skipped => skipped.reason.includes("not published on npm")); + const unpublishable = plan.skipped.filter((skipped) => + skipped.reason.includes("not published on npm"), + ); // Only surface direct-dependency skips here. Transitive findings without an // auto-fix path are already covered by the fix plan step 2 ("Review these urgent // transitive issues"), so repeating them here creates confusing duplication. - const remaining = plan.skipped.filter(skipped => - !skipped.reason.includes("not published on npm") && skipped.relationship === "direct" + const remaining = plan.skipped.filter( + (skipped) => + !skipped.reason.includes("not published on npm") && + skipped.relationship === "direct", ); if (unpublishable.length > 0) { @@ -172,7 +229,11 @@ export function printSuggestedFixCommandSkips( if (remaining.length > 0) { console.log(""); - console.log(chalk.gray("No auto-fix command available for these direct dependencies:")); + console.log( + chalk.gray( + "No auto-fix command available for these direct dependencies:", + ), + ); for (const skipped of remaining.slice(0, 5)) { console.log(`- ${skipped.package}@${skipped.version}: ${skipped.reason}`); } @@ -202,12 +263,26 @@ export function printSkippedDependencies(skipped: string[]) { } } -export function printTable(findings: Finding[], threshold: SeverityLabel | null, skippedKeys?: ReadonlySet) { - const headers = ["Package", "Version", "Severity", "Type", "Usage", "Fixed", "IDs"]; - const rawRows = findings.map(f => { +export function printTable( + findings: Finding[], + threshold: SeverityLabel | null, + skippedKeys?: ReadonlySet, +) { + const headers = [ + "Package", + "Version", + "Severity", + "Type", + "Usage", + "Fixed", + "IDs", + ]; + const rawRows = findings.map((f) => { let usageText = "n/a"; if (f.usage) { - usageText = f.usage.imported ? `${f.usage.files.length} file(s)` : "unused"; + usageText = f.usage.imported + ? `${f.usage.files.length} file(s)` + : "unused"; } const isSkipped = skippedKeys?.has(`${f.pkg.name}@${f.pkg.version}`); const fixVersion = f.validatedFirstFixedVersion ?? f.firstFixedVersion; @@ -219,8 +294,12 @@ export function printTable(findings: Finding[], threshold: SeverityLabel | null, } else if (f.maliciousUnverifiable) { fixedDisplay = chalk.yellow("⚠ Unverifiable (private source)"); } else if (f.maliciousGitSource) { - fixedDisplay = chalk.yellow(f.maliciousGitSourcePinned ? MAL_GIT_SOURCE_PINNED_DISPLAY : MAL_GIT_SOURCE_FLOATING_DISPLAY); - } else if (f.vulnerabilities.some(v => v.id.startsWith("MAL-"))) { + fixedDisplay = chalk.yellow( + f.maliciousGitSourcePinned + ? MAL_GIT_SOURCE_PINNED_DISPLAY + : MAL_GIT_SOURCE_FLOATING_DISPLAY, + ); + } else if (f.vulnerabilities.some((v) => v.id.startsWith("MAL-"))) { fixedDisplay = chalk.yellow("⚠ Malicious"); } else { fixedDisplay = chalk.yellow("⚠ no fix"); @@ -232,21 +311,31 @@ export function printTable(findings: Finding[], threshold: SeverityLabel | null, formatRelLabel(f), usageText, fixedDisplay, - f.vulnerabilities.map(v => v.id).join(", ") + f.vulnerabilities.map((v) => v.id).join(", "), ]; }); const widths = headers.map((header, index) => - Math.min(40, Math.max(header.length, ...rawRows.map(r => stripAnsi(String(r[index])).length))) + Math.min( + 40, + Math.max( + header.length, + ...rawRows.map((r) => stripAnsi(String(r[index])).length), + ), + ), ); console.log(""); if (threshold) { - console.log(chalk.bold(`Showing ${threshold}+ findings in the main table. Use --all to show everything.`)); + console.log( + chalk.bold( + `Showing ${threshold}+ findings in the main table. Use --all to show everything.`, + ), + ); } const line = (left: string, mid: string, right: string) => - left + widths.map(w => "─".repeat(w + 2)).join(mid) + right; + left + widths.map((w) => "─".repeat(w + 2)).join(mid) + right; console.log(line("┌", "┬", "┐")); console.log(renderRow(headers, widths)); @@ -254,8 +343,10 @@ export function printTable(findings: Finding[], threshold: SeverityLabel | null, for (const row of rawRows) { let usageDecorated = String(row[4]); - if (usageDecorated.includes("file(s)")) usageDecorated = chalk.red(usageDecorated); - else if (usageDecorated.includes("unused")) usageDecorated = chalk.green(usageDecorated); + if (usageDecorated.includes("file(s)")) + usageDecorated = chalk.red(usageDecorated); + else if (usageDecorated.includes("unused")) + usageDecorated = chalk.green(usageDecorated); else usageDecorated = chalk.gray(usageDecorated); const decorated = [ @@ -265,39 +356,64 @@ export function printTable(findings: Finding[], threshold: SeverityLabel | null, formatRelationshipLabel(String(row[3])), usageDecorated, row[5], - row[6] + row[6], ]; console.log(renderRow(decorated, widths)); } console.log(line("└", "┴", "┘")); - const maliciousFindings = findings.filter(f => f.vulnerabilities.some(v => v.id.startsWith("MAL-"))); + const maliciousFindings = findings.filter((f) => + f.vulnerabilities.some((v) => v.id.startsWith("MAL-")), + ); if (maliciousFindings.length > 0) { console.log(""); console.log(chalk.bold.red("⚠ Malicious package advisory:")); for (const f of maliciousFindings) { - const action = f.relationship === "direct" - ? "Remove it from your dependencies immediately." - : "Upgrade or remove the parent package that pulls it in."; + const action = + f.relationship === "direct" + ? "Remove it from your dependencies immediately." + : "Upgrade or remove the parent package that pulls it in."; if (f.maliciousUnverifiable) { - console.log(chalk.yellow(` · ${f.pkg.name}@${f.pkg.version} - Unverifiable (private source) - ${action}`)); + console.log( + chalk.yellow( + ` · ${f.pkg.name}@${f.pkg.version} - Unverifiable (private source) - ${action}`, + ), + ); } else if (f.maliciousGitSource) { - const msg = f.maliciousGitSourcePinned ? MAL_GIT_SOURCE_PINNED_MESSAGE : MAL_GIT_SOURCE_FLOATING_MESSAGE; + const msg = f.maliciousGitSourcePinned + ? MAL_GIT_SOURCE_PINNED_MESSAGE + : MAL_GIT_SOURCE_FLOATING_MESSAGE; const url = f.pkg.resolvedUrl ? ` (${f.pkg.resolvedUrl})` : ""; - console.log(chalk.yellow(` · ${f.pkg.name}@${f.pkg.version}${url} - ${msg}`)); + console.log( + chalk.yellow(` · ${f.pkg.name}@${f.pkg.version}${url} - ${msg}`), + ); } else { - console.log(chalk.red(` · ${f.pkg.name}@${f.pkg.version} - ${action}`)); + console.log( + chalk.red(` · ${f.pkg.name}@${f.pkg.version} - ${action}`), + ); } } } - if (skippedKeys && skippedKeys.size > 0 && findings.some(f => skippedKeys.has(`${f.pkg.name}@${f.pkg.version}`))) { - console.log(chalk.gray("⊘ Advisory hint only — no automated fix command could be generated. Run --report to view detailed skip reasons.")); + if ( + skippedKeys && + skippedKeys.size > 0 && + findings.some((f) => skippedKeys.has(`${f.pkg.name}@${f.pkg.version}`)) + ) { + console.log( + chalk.gray( + "⊘ Advisory hint only — no automated fix command could be generated. Run --report to view detailed skip reasons.", + ), + ); } if (threshold) { - console.log(chalk.gray("Tip: use --all to include low findings, or --min-severity high to focus only on urgent issues.")); + console.log( + chalk.gray( + "Tip: use --all to include low findings, or --min-severity high to focus only on urgent issues.", + ), + ); } } @@ -306,26 +422,30 @@ export function printFinalStatus(findings: Finding[]) { console.log(chalk.gray("────────────────────────────────")); if (findings.length === 0) { - console.log(chalk.greenBright("✔ Scan complete. No known vulnerabilities found.")); + console.log( + chalk.greenBright("✔ Scan complete. No known vulnerabilities found."), + ); return; } - const criticalCount = findings.filter(f => f.severity === "critical").length; - const highCount = findings.filter(f => f.severity === "high").length; + const criticalCount = findings.filter( + (f) => f.severity === "critical", + ).length; + const highCount = findings.filter((f) => f.severity === "high").length; if (criticalCount > 0 || highCount > 0) { console.log( chalk.redBright( - `✖ Scan complete. ${findings.length} ${pluralize(findings.length, "issue")} found (${criticalCount} critical, ${highCount} high). Start with the priority fixes above.` - ) + `✖ Scan complete. ${findings.length} ${pluralize(findings.length, "issue")} found (${criticalCount} critical, ${highCount} high). Start with the priority fixes above.`, + ), ); return; } console.log( chalk.yellow( - `▲ Scan complete. ${findings.length} ${pluralize(findings.length, "issue")} found. Review the suggested fix plan above.` - ) + `▲ Scan complete. ${findings.length} ${pluralize(findings.length, "issue")} found. Review the suggested fix plan above.`, + ), ); } @@ -346,7 +466,9 @@ function truncate(value: string, width: number) { function renderWrappedRow(cells: string[], widths: number[]) { const wrappedCells = cells.map((cell, i) => wrapCell(cell, widths[i])); - const rowHeight = Math.max(...wrappedCells.map(cellLines => cellLines.length)); + const rowHeight = Math.max( + ...wrappedCells.map((cellLines) => cellLines.length), + ); const rows: string[] = []; for (let lineIndex = 0; lineIndex < rowHeight; lineIndex++) { @@ -389,9 +511,11 @@ export function printCompactOutput( options?: { offline?: boolean; all?: boolean; subfolder?: string }, ) { console.log(""); - + if (findings.length === 0) { - console.log(chalk.greenBright("✔ Scan complete. No known vulnerabilities found.")); + console.log( + chalk.greenBright("✔ Scan complete. No known vulnerabilities found."), + ); console.log(""); return; } @@ -407,38 +531,55 @@ export function printCompactOutput( for (const finding of urgentFindings) { const sevLabel = finding.severity.toUpperCase().padEnd(8); - const typeLabel = finding.relationship === "direct" - ? `Direct dependency${finding.pkg.dev === true ? " · dev" : ""}` - : finding.relationship === "transitive" - ? `Transitive dependency${finding.pkg.dev === true ? " · dev" : ""}` - : "Unknown dependency"; - + const typeLabel = + finding.relationship === "direct" + ? `Direct dependency${finding.pkg.dev === true ? " · dev" : ""}` + : finding.relationship === "transitive" + ? `Transitive dependency${finding.pkg.dev === true ? " · dev" : ""}` + : "Unknown dependency"; + let usageContext = ""; if (finding.usage) { if (finding.usage.imported) { - usageContext = chalk.red(` (imported in ${finding.usage.files.length} ${pluralize(finding.usage.files.length, "file")})`); + usageContext = chalk.red( + ` (imported in ${finding.usage.files.length} ${pluralize(finding.usage.files.length, "file")})`, + ); } else { usageContext = chalk.green(` (no direct import found)`); } } - - console.log(`${formatSeverityLabel(sevLabel)} ${chalk.whiteBright(finding.pkg.name)}@${finding.pkg.version}`); + + console.log( + `${formatSeverityLabel(sevLabel)} ${chalk.whiteBright(finding.pkg.name)}@${finding.pkg.version}`, + ); console.log(` ${typeLabel}${usageContext}`); - - const isMalicious = finding.vulnerabilities.some(v => v.id.startsWith("MAL-")); + + const isMalicious = finding.vulnerabilities.some((v) => + v.id.startsWith("MAL-"), + ); if (isMalicious) { if (finding.maliciousUnverifiable) { - console.log(` ${chalk.yellow(`⚠ Unverifiable (private source) - ${MAL_PRIVATE_REGISTRY_COMPACT_MESSAGE}`)}`); + console.log( + ` ${chalk.yellow(`⚠ Unverifiable (private source) - ${MAL_PRIVATE_REGISTRY_COMPACT_MESSAGE}`)}`, + ); } else if (finding.maliciousGitSource) { - const url = finding.pkg.resolvedUrl ? ` Source: ${finding.pkg.resolvedUrl}` : ""; - console.log(` ${chalk.yellow(`⚠ ${MAL_GIT_SOURCE_COMPACT_MESSAGE}${url}`)}`); + const url = finding.pkg.resolvedUrl + ? ` Source: ${finding.pkg.resolvedUrl}` + : ""; + console.log( + ` ${chalk.yellow(`⚠ ${MAL_GIT_SOURCE_COMPACT_MESSAGE}${url}`)}`, + ); } else { - const action = finding.relationship === "direct" - ? "Remove this package from your dependencies immediately." - : "Upgrade or remove the parent package that pulls it in."; + const action = + finding.relationship === "direct" + ? "Remove this package from your dependencies immediately." + : "Upgrade or remove the parent package that pulls it in."; console.log(` ${chalk.red(`⚠ Malicious: ${action}`)}`); } - } else if (finding.recommendedNpmTransitiveRemediation?.kind === "update-parent-within-range") { + } else if ( + finding.recommendedNpmTransitiveRemediation?.kind === + "update-parent-within-range" + ) { console.log( ` ${chalk.gray(`Fix: lockfile refresh — ${finding.recommendedNpmTransitiveRemediation.package} already permits a safe version`)}`, ); @@ -447,7 +588,8 @@ export function printCompactOutput( ` ${chalk.gray(`Fix: upgrade ${finding.recommendedParentUpgrade.package} to ${finding.recommendedParentUpgrade.targetVersion}`)}`, ); } else if (finding.firstFixedVersion) { - const displayFixVersion = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + const displayFixVersion = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; let action: string; if (finding.relationship === "direct") { action = `upgrade to ${displayFixVersion}`; @@ -473,16 +615,22 @@ export function printCompactOutput( console.log(""); } - const plan = scanInput ? buildSuggestedFixCommandPlan(findings, scanInput, options) : null; + const plan = scanInput + ? buildSuggestedFixCommandPlan(findings, scanInput, options) + : null; if (plan) { if (plan?.sections.length) { console.log("────────────────────────────────"); console.log(chalk.bold.yellow("🛠 Suggested Fix Commands")); console.log("────────────────────────────────\n"); - console.log(`${chalk.gray("Detected package manager:")} ${chalk.cyan(plan.packageManager)} ${chalk.gray(`(${plan.sourceLabel})`)}`); + console.log( + `${chalk.gray("Detected package manager:")} ${chalk.cyan(plan.packageManager)} ${chalk.gray(`(${plan.sourceLabel})`)}`, + ); console.log(formatFixCommandSummary(plan)); - const compactValidationSummary = summarizeAdjustedValidation(plan.targets); + const compactValidationSummary = summarizeAdjustedValidation( + plan.targets, + ); if (compactValidationSummary.checked > 0) { console.log( chalk.gray( @@ -512,18 +660,21 @@ export function printCompactOutput( console.log("────────────────────────────────\n"); const counts = { - critical: findings.filter(f => f.severity === "critical").length, - high: findings.filter(f => f.severity === "high").length, - medium: findings.filter(f => f.severity === "medium").length, - low: findings.filter(f => f.severity === "low").length, - unknown: findings.filter(f => f.severity === "unknown").length + critical: findings.filter((f) => f.severity === "critical").length, + high: findings.filter((f) => f.severity === "high").length, + medium: findings.filter((f) => f.severity === "medium").length, + low: findings.filter((f) => f.severity === "low").length, + unknown: findings.filter((f) => f.severity === "unknown").length, }; - const direct = findings.filter(f => f.relationship === "direct").length; - const transitive = findings.filter(f => f.relationship === "transitive").length; + const direct = findings.filter((f) => f.relationship === "direct").length; + const transitive = findings.filter( + (f) => f.relationship === "transitive", + ).length; const parts: string[] = []; - if (counts.critical > 0) parts.push(chalk.redBright(`${counts.critical} critical`)); + if (counts.critical > 0) + parts.push(chalk.redBright(`${counts.critical} critical`)); if (counts.high > 0) parts.push(chalk.magenta(`${counts.high} high`)); if (counts.medium > 0) parts.push(chalk.yellow(`${counts.medium} medium`)); if (counts.low > 0) parts.push(chalk.green(`${counts.low} low`)); @@ -534,30 +685,43 @@ export function printCompactOutput( const compactCveLabel = compactCVEs === 1 ? "CVE" : "CVEs"; console.log( `${chalk.whiteBright(String(findings.length))} ${compactPkgLabel}` + - chalk.gray(" · ") + - `${chalk.whiteBright(String(compactCVEs))} ${compactCveLabel}` + chalk.gray(" · ") + + `${chalk.whiteBright(String(compactCVEs))} ${compactCveLabel}`, ); console.log(parts.join(chalk.gray(" · "))); console.log( `${chalk.cyan(String(direct))} ${chalk.white("direct")}` + - `${chalk.gray(" · ")}` + - `${chalk.cyan(String(transitive))} ${chalk.white("transitive")}` + `${chalk.gray(" · ")}` + + `${chalk.cyan(String(transitive))} ${chalk.white("transitive")}`, ); - const maliciousCompact = findings.filter(f => f.vulnerabilities.some(v => v.id.startsWith("MAL-"))); + const maliciousCompact = findings.filter((f) => + f.vulnerabilities.some((v) => v.id.startsWith("MAL-")), + ); if (maliciousCompact.length > 0 && !options?.all) { console.log(""); console.log(chalk.bold.red("⚠ Malicious package advisory:")); for (const f of maliciousCompact) { if (f.maliciousUnverifiable) { - console.log(chalk.yellow(` · ${f.pkg.name}@${f.pkg.version} - Unverifiable (private source) - ${MAL_PRIVATE_REGISTRY_LEGEND_MESSAGE}`)); + console.log( + chalk.yellow( + ` · ${f.pkg.name}@${f.pkg.version} - Unverifiable (private source) - ${MAL_PRIVATE_REGISTRY_LEGEND_MESSAGE}`, + ), + ); } else if (f.maliciousGitSource) { - console.log(chalk.yellow(` · ${f.pkg.name}@${f.pkg.version} - Git source - ${MAL_GIT_SOURCE_LEGEND_MESSAGE}`)); + console.log( + chalk.yellow( + ` · ${f.pkg.name}@${f.pkg.version} - Git source - ${MAL_GIT_SOURCE_LEGEND_MESSAGE}`, + ), + ); } else { - const action = f.relationship === "direct" - ? "Remove it from your dependencies immediately." - : "Upgrade or remove the parent package that pulls it in."; - console.log(chalk.red(` · ${f.pkg.name}@${f.pkg.version} - ${action}`)); + const action = + f.relationship === "direct" + ? "Remove it from your dependencies immediately." + : "Upgrade or remove the parent package that pulls it in."; + console.log( + chalk.red(` · ${f.pkg.name}@${f.pkg.version} - ${action}`), + ); } } } @@ -573,23 +737,33 @@ export function printCompactOutput( if (urgentCount > 0) { console.log( chalk.redBright( - `✖ Scan complete. ${urgentCount} urgent ${pluralize(urgentCount, "issue")} found.` - ) + `✖ Scan complete. ${urgentCount} urgent ${pluralize(urgentCount, "issue")} found.`, + ), ); } else { console.log( chalk.yellow( - `▲ Scan complete. ${findings.length} ${pluralize(findings.length, "issue")} found.` - ) + `▲ Scan complete. ${findings.length} ${pluralize(findings.length, "issue")} found.`, + ), ); } if (!options?.all) { - console.log(chalk.gray(`Run with ${chalk.whiteBright("--verbose")} for fix plan, paths, and full table.`)); + console.log( + chalk.gray( + `Run with ${chalk.whiteBright("--verbose")} for fix plan, paths, and full table.`, + ), + ); } console.log(""); } -function renderSeverityTable(counts: { critical: number; high: number; medium: number; low: number; unknown: number }): string { +function renderSeverityTable(counts: { + critical: number; + high: number; + medium: number; + low: number; + unknown: number; +}): string { const labels = ["Critical", "High", "Medium", "Low", "Unknown"]; const coloredValues = [ chalk.redBright(String(counts.critical)), @@ -605,24 +779,40 @@ function renderSeverityTable(counts: { critical: number; high: number; medium: n String(counts.low), String(counts.unknown), ]; - const widths = labels.map((label, i) => Math.max(label.length, rawValues[i].length)); + const widths = labels.map((label, i) => + Math.max(label.length, rawValues[i].length), + ); const line = (left: string, mid: string, right: string) => - left + widths.map(w => "─".repeat(w + 2)).join(mid) + right; + left + widths.map((w) => "─".repeat(w + 2)).join(mid) + right; const pad = (text: string, raw: string, width: number) => { const pad = width - raw.length; const left = Math.floor(pad / 2); const right = pad - left; return " ".repeat(left + 1) + text + " ".repeat(right + 1); }; - const headerRow = "│" + labels.map((label, i) => ` ${label.padStart(Math.floor((widths[i] - label.length) / 2) + label.length).padEnd(widths[i])} `).join("│") + "│"; - const valueRow = "│" + coloredValues.map((val, i) => pad(val, rawValues[i], widths[i])).join("│") + "│"; - return [line("┌", "┬", "┐"), headerRow, line("├", "┼", "┤"), valueRow, line("└", "┴", "┘")].join("\n"); + const headerRow = + "│" + + labels + .map( + (label, i) => + ` ${label.padStart(Math.floor((widths[i] - label.length) / 2) + label.length).padEnd(widths[i])} `, + ) + .join("│") + + "│"; + const valueRow = + "│" + + coloredValues.map((val, i) => pad(val, rawValues[i], widths[i])).join("│") + + "│"; + return [ + line("┌", "┬", "┐"), + headerRow, + line("├", "┼", "┤"), + valueRow, + line("└", "┴", "┘"), + ].join("\n"); } -function colorFixSectionTitle( - severity: SeverityLabel, - title: string, -) { +function colorFixSectionTitle(severity: SeverityLabel, title: string) { if (severity === "critical") return chalk.redBright(title); if (severity === "high") return chalk.magenta(title); if (severity === "medium") return chalk.yellow(title); @@ -640,12 +830,17 @@ function formatFixCommandSummary( const severityCounts = new Map(); for (const section of plan.sections) { - severityCounts.set(section.severity, (severityCounts.get(section.severity) ?? 0) + 1); + severityCounts.set( + section.severity, + (severityCounts.get(section.severity) ?? 0) + 1, + ); } - const severitySummary = (["critical", "high", "medium", "low", "unknown"] as SeverityLabel[]) - .filter(severity => (severityCounts.get(severity) ?? 0) > 0) - .map(severity => `${severityCounts.get(severity)} ${severity}`) + const severitySummary = ( + ["critical", "high", "medium", "low", "unknown"] as SeverityLabel[] + ) + .filter((severity) => (severityCounts.get(severity) ?? 0) > 0) + .map((severity) => `${severityCounts.get(severity)} ${severity}`) .join(", "); const packageLabel = packageCount === 1 ? "package" : "packages"; @@ -658,14 +853,17 @@ function renderCommandCallout(command: string) { return chalk.bold.cyan(`> ${command}`); } - function summarizeAdjustedValidation( - targets: Array<{ scannedVersions?: number | null; knownVulnerableVersions?: number | null }>, + targets: Array<{ + scannedVersions?: number | null; + knownVulnerableVersions?: number | null; + }>, ): { checked: number; vulnerable: number } { let checked = 0; let vulnerable = 0; for (const target of targets) { - if (target.scannedVersions === null || target.scannedVersions === undefined) continue; + if (target.scannedVersions === null || target.scannedVersions === undefined) + continue; checked += target.scannedVersions; vulnerable += target.knownVulnerableVersions ?? 0; } @@ -686,8 +884,16 @@ function printDirectTargetsTable( validationSummary: { checked: number; vulnerable: number } | null, widthsOverride?: number[], ): void { - const headers = ["Package", "Current", "Target", "Usage", "Versions scanned", "Still known vulnerable", "Breaking?"]; - const rows = targets.map(target => { + const headers = [ + "Package", + "Current", + "Target", + "Usage", + "Versions scanned", + "Still known vulnerable", + "Breaking?", + ]; + const rows = targets.map((target) => { if (target.adjustmentNote) { const isCountedVulnerabilityNote = target.scannedVersions !== null && @@ -698,10 +904,14 @@ function printDirectTargetsTable( } } - const isBreaking = !!target.currentVersion && isMajorVersionBump(target.currentVersion, target.targetVersion); + const isBreaking = + !!target.currentVersion && + isMajorVersionBump(target.currentVersion, target.targetVersion); let usageText = chalk.gray("n/a"); if (target.usage) { - usageText = target.usage.imported ? chalk.red(`${target.usage.files.length} file(s)`) : chalk.green("unused"); + usageText = target.usage.imported + ? chalk.red(`${target.usage.files.length} file(s)`) + : chalk.green("unused"); } return [ @@ -712,7 +922,8 @@ function printDirectTargetsTable( target.scannedVersions !== null && target.scannedVersions !== undefined ? chalk.blue(String(target.scannedVersions)) : "-", - target.knownVulnerableVersions !== null && target.knownVulnerableVersions !== undefined + target.knownVulnerableVersions !== null && + target.knownVulnerableVersions !== undefined ? target.knownVulnerableVersions > 0 ? chalk.yellow(String(target.knownVulnerableVersions)) : chalk.green(String(target.knownVulnerableVersions)) @@ -726,13 +937,18 @@ function printDirectTargetsTable( const widths = widthsOverride ?? computeTableWidths(headers, rows); const line = (left: string, mid: string, right: string) => - left + widths.map(w => "─".repeat(w + 2)).join(mid) + right; + left + widths.map((w) => "─".repeat(w + 2)).join(mid) + right; console.log(line("┌", "┬", "┐")); console.log(renderRow(headers, widths)); console.log(line("├", "┼", "┤")); for (const row of rows) { - console.log(renderRow(row.map(value => String(value)), widths)); + console.log( + renderRow( + row.map((value) => String(value)), + widths, + ), + ); } if (validationSummary) { console.log(line("├", "┼", "┤")); @@ -768,11 +984,19 @@ function printParentUpgradeTargetsTable( }>, widthsOverride?: number[], ): void { - const headers = ["Package", "Current", "Recommended target", "Usage", "Context"]; - const rows = targets.map(target => { + const headers = [ + "Package", + "Current", + "Recommended target", + "Usage", + "Context", + ]; + const rows = targets.map((target) => { let usageText = chalk.gray("n/a"); if (target.usage) { - usageText = target.usage.imported ? chalk.red(`${target.usage.files.length} file(s)`) : chalk.green("unused"); + usageText = target.usage.imported + ? chalk.red(`${target.usage.files.length} file(s)`) + : chalk.green("unused"); } return [ target.package, @@ -785,31 +1009,51 @@ function printParentUpgradeTargetsTable( if (rows.length === 0) return; // Allow the Context column (index 4) up to 60 chars so reason text is not truncated. - const widths = widthsOverride ?? computeTableWidths(headers, rows, [40, 40, 40, 40, 60]); + const widths = + widthsOverride ?? computeTableWidths(headers, rows, [40, 40, 40, 40, 60]); const line = (left: string, mid: string, right: string) => - left + widths.map(w => "─".repeat(w + 2)).join(mid) + right; + left + widths.map((w) => "─".repeat(w + 2)).join(mid) + right; console.log(line("┌", "┬", "┐")); console.log(renderRow(headers, widths)); console.log(line("├", "┼", "┤")); for (const row of rows) { - for (const outputRow of renderWrappedRow(row.map(value => String(value)), widths)) { + for (const outputRow of renderWrappedRow( + row.map((value) => String(value)), + widths, + )) { console.log(outputRow); } } console.log(line("└", "┴", "┘")); } -function computeTableWidths(headers: string[], rows: string[][], columnMaxWidths?: number[]): number[] { +function computeTableWidths( + headers: string[], + rows: string[][], + columnMaxWidths?: number[], +): number[] { return headers.map((header, index) => { const maxWidth = columnMaxWidths?.[index] ?? 40; - return Math.min(maxWidth, Math.max(header.length, ...rows.map(row => stripAnsi(String(row[index])).length))); + return Math.min( + maxWidth, + Math.max( + header.length, + ...rows.map((row) => stripAnsi(String(row[index])).length), + ), + ); }); } function computeSharedDirectTableWidths( sections: Array<{ - kind: "urgent" | "direct" | "direct-adjusted" | "parent-upgrade" | "parent-update" | "validated-chain-upgrade"; + kind: + | "urgent" + | "direct" + | "direct-adjusted" + | "parent-upgrade" + | "parent-update" + | "validated-chain-upgrade"; targets: Array<{ package: string; currentVersion?: string; @@ -820,32 +1064,60 @@ function computeSharedDirectTableWidths( }>; }>, ): number[] | undefined { - const directSections = sections.filter(section => section.kind === "direct" || section.kind === "direct-adjusted"); + const directSections = sections.filter( + (section) => + section.kind === "direct" || section.kind === "direct-adjusted", + ); if (directSections.length === 0) return undefined; - const headers = ["Package", "Current", "Target", "Usage", "Versions scanned", "Still known vulnerable", "Breaking?"]; + const headers = [ + "Package", + "Current", + "Target", + "Usage", + "Versions scanned", + "Still known vulnerable", + "Breaking?", + ]; const rows: string[][] = []; for (const section of directSections) { for (const target of section.targets) { - const isBreaking = !!target.currentVersion && isMajorVersionBump(target.currentVersion, target.targetVersion); + const isBreaking = + !!target.currentVersion && + isMajorVersionBump(target.currentVersion, target.targetVersion); let usageText = "n/a"; if (target.usage) { - usageText = target.usage.imported ? `${target.usage.files.length} file(s)` : "unused"; + usageText = target.usage.imported + ? `${target.usage.files.length} file(s)` + : "unused"; } rows.push([ target.package, target.currentVersion ?? "-", target.targetVersion, usageText, - target.scannedVersions !== null && target.scannedVersions !== undefined ? String(target.scannedVersions) : "-", - target.knownVulnerableVersions !== null && target.knownVulnerableVersions !== undefined ? String(target.knownVulnerableVersions) : "-", + target.scannedVersions !== null && target.scannedVersions !== undefined + ? String(target.scannedVersions) + : "-", + target.knownVulnerableVersions !== null && + target.knownVulnerableVersions !== undefined + ? String(target.knownVulnerableVersions) + : "-", isBreaking ? "⚠" : "", ]); } const summary = summarizeAdjustedValidation(section.targets); - rows.push(["Total", "-", "-", "-", String(summary.checked), String(summary.vulnerable), ""]); + rows.push([ + "Total", + "-", + "-", + "-", + String(summary.checked), + String(summary.vulnerable), + "", + ]); } return computeTableWidths(headers, rows); @@ -854,12 +1126,24 @@ function computeSharedDirectTableWidths( function shouldRenderParentUpgradeTable( targets: Array<{ kind: "direct" | "parent-upgrade" | "parent-update" }>, ): boolean { - return targets.length > 0 && targets.every(target => target.kind === "parent-upgrade" || target.kind === "parent-update"); + return ( + targets.length > 0 && + targets.every( + (target) => + target.kind === "parent-upgrade" || target.kind === "parent-update", + ) + ); } function computeSharedParentUpgradeTableWidths( sections: Array<{ - kind: "urgent" | "direct" | "direct-adjusted" | "parent-upgrade" | "parent-update" | "validated-chain-upgrade"; + kind: + | "urgent" + | "direct" + | "direct-adjusted" + | "parent-upgrade" + | "parent-update" + | "validated-chain-upgrade"; targets: Array<{ package: string; currentVersion?: string; @@ -871,16 +1155,26 @@ function computeSharedParentUpgradeTableWidths( }>; }>, ): number[] | undefined { - const parentSections = sections.filter(section => shouldRenderParentUpgradeTable(section.targets)); + const parentSections = sections.filter((section) => + shouldRenderParentUpgradeTable(section.targets), + ); if (parentSections.length === 0) return undefined; - const headers = ["Package", "Current", "Recommended target", "Usage", "Context"]; + const headers = [ + "Package", + "Current", + "Recommended target", + "Usage", + "Context", + ]; const rows: string[][] = []; for (const section of parentSections) { for (const target of section.targets) { let usageText = "n/a"; if (target.usage) { - usageText = target.usage.imported ? `${target.usage.files.length} file(s)` : "unused"; + usageText = target.usage.imported + ? `${target.usage.files.length} file(s)` + : "unused"; } rows.push([ target.package, diff --git a/src/output/sarif.ts b/src/output/sarif.ts index 2324524b..ee014ff1 100644 --- a/src/output/sarif.ts +++ b/src/output/sarif.ts @@ -62,10 +62,13 @@ const LOCKFILE_NAMES: Record = { "yarn-lock": "yarn.lock", "bun-lock": "bun.lockb", "package-json": "package.json", - "unknown": "lockfile", + unknown: "lockfile", }; -export function deriveLockfileUri(scanInput: { filePath: string | null; source: ScanSource }): string { +export function deriveLockfileUri(scanInput: { + filePath: string | null; + source: ScanSource; +}): string { if (scanInput.filePath) return path.basename(scanInput.filePath); return LOCKFILE_NAMES[scanInput.source] ?? "lockfile"; } @@ -90,9 +93,10 @@ export function buildSarifOutput( }, }; - const ruleIds = finding.cveAliases.length > 0 - ? finding.cveAliases - : finding.vulnerabilities.map(v => v.id); + const ruleIds = + finding.cveAliases.length > 0 + ? finding.cveAliases + : finding.vulnerabilities.map((v) => v.id); for (const ruleId of ruleIds) { if (!ruleMap.has(ruleId)) { @@ -119,7 +123,8 @@ export function buildSarifOutput( } return { - $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + $schema: + "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", version: "2.1.0", runs: [ { @@ -132,9 +137,7 @@ export function buildSarifOutput( }, }, results, - artifacts: [ - { location: { uri: lockfileUri, uriBaseId: "%SRCROOT%" } }, - ], + artifacts: [{ location: { uri: lockfileUri, uriBaseId: "%SRCROOT%" } }], }, ], }; @@ -152,8 +155,15 @@ export function writeSarifReport( try { fs.writeFileSync(outputPath, JSON.stringify(sarif, null, 2)); } catch (err) { - try { fs.rmSync(outputPath, { force: true }); } catch { /* best-effort cleanup */ } - throw new Error(`Failed to write SARIF report to ${outputPath}: ${err instanceof Error ? err.message : String(err)}`, { cause: err }); + try { + fs.rmSync(outputPath, { force: true }); + } catch { + /* best-effort cleanup */ + } + throw new Error( + `Failed to write SARIF report to ${outputPath}: ${err instanceof Error ? err.message : String(err)}`, + { cause: err }, + ); } return filename; } diff --git a/src/output/spinner.ts b/src/output/spinner.ts index 760fb6be..8b003527 100644 --- a/src/output/spinner.ts +++ b/src/output/spinner.ts @@ -2,14 +2,17 @@ import process from "node:process"; import type { Spinner } from "../types.js"; import { chalk } from "../utils/chalk.js"; -export function createSpinner(initialMessage: string, options?: { json?: boolean }): Spinner { +export function createSpinner( + initialMessage: string, + options?: { json?: boolean }, +): Spinner { const enabled = Boolean(process.stdout.isTTY) && !options?.json; if (!enabled) { return { update: () => {}, succeed: () => {}, fail: () => {}, - stop: () => {} + stop: () => {}, }; } @@ -18,7 +21,9 @@ export function createSpinner(initialMessage: string, options?: { json?: boolean let currentMessage = initialMessage; const render = () => { - process.stdout.write(`\r\x1b[2K${chalk.cyan(frames[frameIndex])} ${currentMessage}`); + process.stdout.write( + `\r\x1b[2K${chalk.cyan(frames[frameIndex])} ${currentMessage}`, + ); frameIndex = (frameIndex + 1) % frames.length; }; @@ -45,6 +50,6 @@ export function createSpinner(initialMessage: string, options?: { json?: boolean }, stop() { clearLine(); - } + }, }; } diff --git a/src/output/write-outputs.ts b/src/output/write-outputs.ts index e2431817..024da770 100644 --- a/src/output/write-outputs.ts +++ b/src/output/write-outputs.ts @@ -1,7 +1,12 @@ import fs from "node:fs"; import path from "node:path"; import { chalk } from "../utils/chalk.js"; -import type { ParsedOptions, ScanInput, Finding, PackageRef } from "../types.js"; +import type { + ParsedOptions, + ScanInput, + Finding, + PackageRef, +} from "../types.js"; import type { SuggestedFixCommandPlan } from "../remediation/fix-commands.js"; import type { ProjectMeta } from "./cyclonedx.js"; import { serializeFinding } from "./formatters.js"; @@ -43,30 +48,52 @@ export async function writeOutputs( const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); const jsonFilename = `cve-lite-scan-${ts}.json`; const jsonOutputPath = path.join(process.cwd(), jsonFilename); - fs.writeFileSync(jsonOutputPath, JSON.stringify({ - projectPath, - mode: scanInput.mode, - source: scanInput.source, - packageCount: scanInput.packages.length, - findingCount: scanState.sorted.length, - suggestedFixCommands: scanState.suggestedFixCommands, - notes: [...scanInput.notes, ...scanState.coverage], - warnings: scanInput.warnings, - skippedDependencies: scanInput.skippedDependencies, - findings: scanState.sorted.map(finding => serializeFinding(finding, scanState.suggestedFixCommands)), - }, null, 2)); + fs.writeFileSync( + jsonOutputPath, + JSON.stringify( + { + projectPath, + mode: scanInput.mode, + source: scanInput.source, + packageCount: scanInput.packages.length, + findingCount: scanState.sorted.length, + suggestedFixCommands: scanState.suggestedFixCommands, + notes: [...scanInput.notes, ...scanState.coverage], + warnings: scanInput.warnings, + skippedDependencies: scanInput.skippedDependencies, + findings: scanState.sorted.map((finding) => + serializeFinding(finding, scanState.suggestedFixCommands), + ), + }, + null, + 2, + ), + ); console.log(`${chalk.gray("JSON saved to")} ${chalk.cyan(jsonFilename)}`); } if (options.sarif) { const lockfileUri = deriveLockfileUri(scanInput); - const sarifFilename = writeSarifReport(scanState.sorted, lockfileUri, scanState.suggestedFixCommands); - console.log(`${chalk.gray("SARIF report written to")} ${chalk.cyan(sarifFilename)}`); + const sarifFilename = writeSarifReport( + scanState.sorted, + lockfileUri, + scanState.suggestedFixCommands, + ); + console.log( + `${chalk.gray("SARIF report written to")} ${chalk.cyan(sarifFilename)}`, + ); } if (options.cdx) { const projectMeta = readProjectMeta(projectPath); - const cdxFilename = writeCycloneDxReport(scanState.allPackages, scanState.sorted, scanState.suggestedFixCommands, projectMeta); - console.log(`${chalk.gray("CycloneDX BOM written to")} ${chalk.cyan(cdxFilename)}`); + const cdxFilename = writeCycloneDxReport( + scanState.allPackages, + scanState.sorted, + scanState.suggestedFixCommands, + projectMeta, + ); + console.log( + `${chalk.gray("CycloneDX BOM written to")} ${chalk.cyan(cdxFilename)}`, + ); } } diff --git a/src/parsers/bun-lock.ts b/src/parsers/bun-lock.ts index 00083f21..267a261e 100644 --- a/src/parsers/bun-lock.ts +++ b/src/parsers/bun-lock.ts @@ -17,7 +17,11 @@ function extractChildDependencyNames(depMeta: unknown): string[] { const names = new Set(); const meta = depMeta as Record; - for (const section of ["dependencies", "optionalDependencies", "devDependencies"]) { + for (const section of [ + "dependencies", + "optionalDependencies", + "devDependencies", + ]) { const sectionObj = meta[section]; if (!sectionObj || typeof sectionObj !== "object") continue; for (const name of Object.keys(sectionObj)) { @@ -26,7 +30,12 @@ function extractChildDependencyNames(depMeta: unknown): string[] { } for (const [key, value] of Object.entries(meta)) { - if (key === "dependencies" || key === "optionalDependencies" || key === "devDependencies") continue; + if ( + key === "dependencies" || + key === "optionalDependencies" || + key === "devDependencies" + ) + continue; if (typeof value === "string") { names.add(key); } @@ -41,7 +50,10 @@ function collectBunPaths( map: Map, packageNameToVersion: Map, ): void { - const queue = rootDepNames.map(name => ({ name, path: ["project"] as string[] })); + const queue = rootDepNames.map((name) => ({ + name, + path: ["project"] as string[], + })); const visitedStates = new Set(); let head = 0; @@ -56,7 +68,10 @@ function collectBunPaths( if (version) { const pkg = map.get(`${current.name}@${version}`); if (pkg) { - pkg.paths = uniquePathArrays([...(pkg.paths ?? []), nextPath]).slice(0, MAX_PATHS_PER_PACKAGE); + pkg.paths = uniquePathArrays([...(pkg.paths ?? []), nextPath]).slice( + 0, + MAX_PATHS_PER_PACKAGE, + ); } } @@ -74,7 +89,11 @@ export function buildBunWorkspaceMap(filePath: string): Map { const map = new Map(); for (const [workspacePath, workspace] of Object.entries(workspaces)) { - for (const depSectionName of ["dependencies", "optionalDependencies", "devDependencies"]) { + for (const depSectionName of [ + "dependencies", + "optionalDependencies", + "devDependencies", + ]) { const depSection = (workspace as any)?.[depSectionName]; if (!depSection || typeof depSection !== "object") continue; for (const depName of Object.keys(depSection)) { @@ -89,7 +108,10 @@ export function buildBunWorkspaceMap(filePath: string): Map { return map; } -export function loadFromBunLock(filePath: string, prodOnly: boolean): PackageRef[] { +export function loadFromBunLock( + filePath: string, + prodOnly: boolean, +): PackageRef[] { const raw = parseJsonc(fs.readFileSync(filePath, "utf8")) as any; const packages = raw?.packages ?? {}; const workspaces = raw?.workspaces ?? {}; @@ -112,7 +134,11 @@ export function loadFromBunLock(filePath: string, prodOnly: boolean): PackageRef for (const [workspacePath, workspace] of Object.entries(workspaces)) { if (workspacePath !== "") continue; - for (const depSectionName of ["dependencies", "optionalDependencies", "devDependencies"]) { + for (const depSectionName of [ + "dependencies", + "optionalDependencies", + "devDependencies", + ]) { if (prodOnly && depSectionName === "devDependencies") continue; const depSection = workspace?.[depSectionName]; if (!depSection || typeof depSection !== "object") continue; @@ -140,7 +166,8 @@ export function loadFromBunLock(filePath: string, prodOnly: boolean): PackageRef const dev = devNames.has(pkgName) && !prodNames.has(pkgName); if (prodOnly && dev) continue; - const resolvedUrl = typeof entry[1] === "string" && entry[1] !== "" ? entry[1] : undefined; + const resolvedUrl = + typeof entry[1] === "string" && entry[1] !== "" ? entry[1] : undefined; upsertPackage(map, { name, version, @@ -151,7 +178,12 @@ export function loadFromBunLock(filePath: string, prodOnly: boolean): PackageRef }); } - collectBunPaths(rootDepNames, childDepsByPackageName, map, packageNameToVersion); + collectBunPaths( + rootDepNames, + childDepsByPackageName, + map, + packageNameToVersion, + ); for (const [pkgKey, pkg] of map.entries()) { if ((pkg.paths?.length ?? 0) === 0) { diff --git a/src/parsers/index.ts b/src/parsers/index.ts index 75bb3d3c..28452bf8 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -1,14 +1,24 @@ import fs from "node:fs"; import path from "node:path"; import type { ScanInput } from "../types.js"; -import { findFiles, findNearestPackageJson, chooseBestLockfile, safeReadText, relativeOrName } from "../utils/file.js"; +import { + findFiles, + findNearestPackageJson, + chooseBestLockfile, + safeReadText, + relativeOrName, +} from "../utils/file.js"; import { loadFromBunLock } from "./bun-lock.js"; import { loadFromPackageJson } from "./package-json.js"; import { loadFromPackageLock } from "./package-lock.js"; import { loadFromPnpmLock } from "./pnpm-lock.js"; import { loadFromYarnLock } from "./yarn-lock.js"; -export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: number): ScanInput { +export function loadPackages( + projectRoot: string, + prodOnly: boolean, + maxDepth: number, +): ScanInput { const rootBunLock = path.join(projectRoot, "bun.lock"); const rootShrinkwrap = path.join(projectRoot, "npm-shrinkwrap.json"); const rootPackageLock = path.join(projectRoot, "package-lock.json"); @@ -23,10 +33,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n packages: loadFromBunLock(rootBunLock, prodOnly), notes: [ "Scanned resolved dependency versions from bun.lock.", - "Dependency paths are derived from bun.lock package relationships." + "Dependency paths are derived from bun.lock package relationships.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } @@ -38,10 +48,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n packages: loadFromPackageLock(rootShrinkwrap, prodOnly), notes: [ "Scanned resolved dependency versions from npm-shrinkwrap.json.", - "Dependency paths are derived from lockfile package locations." + "Dependency paths are derived from lockfile package locations.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } @@ -53,10 +63,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n packages: loadFromPackageLock(rootPackageLock, prodOnly), notes: [ "Scanned resolved dependency versions from package-lock.json.", - "Dependency paths are derived from lockfile package locations." + "Dependency paths are derived from lockfile package locations.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } @@ -68,10 +78,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n packages: loadFromPnpmLock(rootPnpmLock, prodOnly), notes: [ "Scanned resolved dependency versions from pnpm-lock.yaml.", - "Dependency paths are approximated from importer relationships and package snapshots." + "Dependency paths are approximated from importer relationships and package snapshots.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } @@ -83,14 +93,24 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n packages: loadFromYarnLock(rootYarnLock), notes: [ "Scanned resolved dependency versions from yarn.lock.", - "Dependency paths are derived from yarn.lock package relationships." + "Dependency paths are derived from yarn.lock package relationships.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } - const discoveredLockfiles = findFiles(projectRoot, ["bun.lock", "npm-shrinkwrap.json", "package-lock.json", "pnpm-lock.yaml", "yarn.lock"], maxDepth); + const discoveredLockfiles = findFiles( + projectRoot, + [ + "bun.lock", + "npm-shrinkwrap.json", + "package-lock.json", + "pnpm-lock.yaml", + "yarn.lock", + ], + maxDepth, + ); if (discoveredLockfiles.length > 0) { const selected = chooseBestLockfile(discoveredLockfiles); const selectedName = path.basename(selected); @@ -103,10 +123,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n notes: [ `Scanned resolved dependency versions from ${relativeOrName(projectRoot, selected)}.`, "Dependency paths are derived from bun.lock package relationships.", - "No supported lockfile was found at the repo root — a nested lockfile was used instead." + "No supported lockfile was found at the repo root — a nested lockfile was used instead.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } if (selectedName === "npm-shrinkwrap.json") { @@ -118,10 +138,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n notes: [ `Scanned resolved dependency versions from ${relativeOrName(projectRoot, selected)}.`, "Dependency paths are derived from lockfile package locations.", - "No supported lockfile was found at the repo root — a nested lockfile was used instead." + "No supported lockfile was found at the repo root — a nested lockfile was used instead.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } if (selectedName === "package-lock.json") { @@ -133,10 +153,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n notes: [ `Scanned resolved dependency versions from ${relativeOrName(projectRoot, selected)}.`, "Dependency paths are derived from lockfile package locations.", - "No supported lockfile was found at the repo root — a nested lockfile was used instead." + "No supported lockfile was found at the repo root — a nested lockfile was used instead.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } if (selectedName === "pnpm-lock.yaml") { @@ -148,10 +168,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n notes: [ `Scanned resolved dependency versions from ${relativeOrName(projectRoot, selected)}.`, "Dependency paths are approximated from importer relationships and package snapshots.", - "No supported lockfile was found at the repo root — a nested lockfile was used instead." + "No supported lockfile was found at the repo root — a nested lockfile was used instead.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } return { @@ -162,10 +182,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n notes: [ `Scanned resolved dependency versions from ${relativeOrName(projectRoot, selected)}.`, "Dependency paths are derived from yarn.lock package relationships.", - "No supported lockfile was found at the repo root — a nested lockfile was used instead." + "No supported lockfile was found at the repo root — a nested lockfile was used instead.", ], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } @@ -174,7 +194,7 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n const manifestResult = loadFromPackageJson(packageJsonPath, prodOnly); const warnings = [ "No supported lockfile was found, so the scanner fell back to package.json.", - "Manifest fallback can only check direct dependencies pinned to exact versions." + "Manifest fallback can only check direct dependencies pinned to exact versions.", ]; const npmrcPath = path.join(path.dirname(packageJsonPath), ".npmrc"); @@ -182,7 +202,7 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n const npmrc = safeReadText(npmrcPath); if (/^\s*package-lock\s*=\s*false\s*$/m.test(npmrc)) { warnings.push( - "This repo disables package-lock generation in .npmrc. For npm projects, try: npm install --package-lock-only --ignore-scripts --package-lock=true" + "This repo disables package-lock generation in .npmrc. For npm projects, try: npm install --package-lock-only --ignore-scripts --package-lock=true", ); } } @@ -194,10 +214,10 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n packages: manifestResult.packages, notes: [ `Scanned direct dependencies from ${relativeOrName(projectRoot, packageJsonPath)}.`, - "Manifest fallback does not resolve transitive dependencies unless they are pinned and present in a lockfile." + "Manifest fallback does not resolve transitive dependencies unless they are pinned and present in a lockfile.", ], warnings, - skippedDependencies: manifestResult.skippedDependencies + skippedDependencies: manifestResult.skippedDependencies, }; } @@ -208,7 +228,7 @@ export function loadPackages(projectRoot: string, prodOnly: boolean, maxDepth: n packages: [], notes: [], warnings: [], - skippedDependencies: [] + skippedDependencies: [], }; } @@ -216,6 +236,6 @@ export function buildNoPackagesMessage(projectRoot: string): string { return [ "No scannable packages were found.", "Supported inputs: bun.lock, npm-shrinkwrap.json, package-lock.json, pnpm-lock.yaml, yarn.lock, or package.json with exact pinned versions.", - `Searched under: ${projectRoot}` + `Searched under: ${projectRoot}`, ].join(" "); } diff --git a/src/parsers/multi-package.ts b/src/parsers/multi-package.ts index b17d61d4..09f4038a 100644 --- a/src/parsers/multi-package.ts +++ b/src/parsers/multi-package.ts @@ -4,13 +4,24 @@ import type { ScanInput } from "../types.js"; import { EXCLUDED_DIRS } from "../constants.js"; import { loadPackages } from "./index.js"; -const LOCKFILE_NAMES = ["bun.lock", "npm-shrinkwrap.json", "package-lock.json", "pnpm-lock.yaml", "yarn.lock"]; +const LOCKFILE_NAMES = [ + "bun.lock", + "npm-shrinkwrap.json", + "package-lock.json", + "pnpm-lock.yaml", + "yarn.lock", +]; export function hasRootLockfile(projectRoot: string): boolean { - return LOCKFILE_NAMES.some(name => fs.existsSync(path.join(projectRoot, name))); + return LOCKFILE_NAMES.some((name) => + fs.existsSync(path.join(projectRoot, name)), + ); } -export function findNestedLockfiles(projectRoot: string, maxDepth: number): string[] { +export function findNestedLockfiles( + projectRoot: string, + maxDepth: number, +): string[] { const results: string[] = []; function walk(dir: string, depth: number): void { @@ -23,7 +34,9 @@ export function findNestedLockfiles(projectRoot: string, maxDepth: number): stri return; } - const lockfile = entries.find(e => e.isFile() && LOCKFILE_NAMES.includes(e.name)); + const lockfile = entries.find( + (e) => e.isFile() && LOCKFILE_NAMES.includes(e.name), + ); if (lockfile) { results.push(path.join(dir, lockfile.name)); return; // stop recursing — this lockfile covers its subtree @@ -58,7 +71,7 @@ export function loadMultiplePackages( maxDepth: number, ): Array<{ scanInput: ScanInput; subfolder: string }> { const lockfiles = findNestedLockfiles(projectRoot, maxDepth); - return lockfiles.map(lockfilePath => { + return lockfiles.map((lockfilePath) => { const subfolderAbs = path.dirname(lockfilePath); const subfolder = path.relative(projectRoot, subfolderAbs); const scanInput = loadPackages(subfolderAbs, prodOnly, maxDepth); diff --git a/src/parsers/npm-lock-graph.ts b/src/parsers/npm-lock-graph.ts index 2a67391b..8a7503be 100644 --- a/src/parsers/npm-lock-graph.ts +++ b/src/parsers/npm-lock-graph.ts @@ -20,7 +20,9 @@ export function loadNpmLockGraph( ): NpmLockGraph { const raw = JSON.parse(fs.readFileSync(filePath, "utf8")); const packages = raw?.packages; - const rawPackages = isRecord(packages) ? (packages as Record) : {}; + const rawPackages = isRecord(packages) + ? (packages as Record) + : {}; const nodesById = new Map(); const nodeIdsByPackageKey = new Map>(); const nodeIdByPackagePath = new Map(); @@ -43,7 +45,9 @@ export function loadNpmLockGraph( }); } - for (const [packagePath, meta] of Object.entries(rawPackages)) { + for (const [packagePath, meta] of Object.entries( + rawPackages, + )) { if (!packagePath || packagePath === "") continue; if (!packagePath.includes("node_modules/")) continue; @@ -68,7 +72,10 @@ export function loadNpmLockGraph( const keySet = nodeIdsByPackageKey.get(packageKey) ?? new Set(); keySet.add(id); nodeIdsByPackageKey.set(packageKey, keySet); - dependencyRangesByNodeId.set(id, collectDependencyRanges(resolveDependencySource(meta, rawPackages))); + dependencyRangesByNodeId.set( + id, + collectDependencyRanges(resolveDependencySource(meta, rawPackages)), + ); resolutionBasePathsByNodeId.set(id, resolveBasePaths(packagePath, meta)); } @@ -98,10 +105,12 @@ export function loadNpmLockGraph( if (options?.includePaths !== false) { const MAX_PATHS_PER_NODE = 5; const MAX_PATH_DEPTH = 10; - const queue: { nodeId: string; path: string[] }[] = entryPackages.map((entryNodeId) => ({ - nodeId: entryNodeId, - path: ["project", nodesById.get(entryNodeId)?.name ?? entryNodeId], - })); + const queue: { nodeId: string; path: string[] }[] = entryPackages.map( + (entryNodeId) => ({ + nodeId: entryNodeId, + path: ["project", nodesById.get(entryNodeId)?.name ?? entryNodeId], + }), + ); let queueHead = 0; while (queueHead < queue.length) { @@ -111,7 +120,9 @@ export function loadNpmLockGraph( if (current.path.length >= MAX_PATH_DEPTH) continue; - for (const childNodeId of childNodeIdsByParentNodeId.get(current.nodeId) ?? []) { + for (const childNodeId of childNodeIdsByParentNodeId.get( + current.nodeId, + ) ?? []) { const childNode = nodesById.get(childNodeId); if (!childNode) continue; @@ -179,7 +190,10 @@ function createGraph(args: { return { entryPackages, nodeIdsFor(name: string, version: string | null): readonly string[] { - return frozenNodeIdsByPackageKey.get(buildPackageKey(name, version)) ?? EMPTY_ARRAY; + return ( + frozenNodeIdsByPackageKey.get(buildPackageKey(name, version)) ?? + EMPTY_ARRAY + ); }, getNode(nodeId: string): Readonly | null { return frozenNodesById.get(nodeId) ?? null; @@ -194,8 +208,9 @@ function createGraph(args: { return args.rangeByParentNodeId.get(parentNodeId)?.get(childName) ?? null; }, pathsFor(nodeId: string): string[][] { - return [...(args.pathSetByNodeId.get(nodeId) ?? new Set())] - .map((item) => item.split(">")); + return [...(args.pathSetByNodeId.get(nodeId) ?? new Set())].map( + (item) => item.split(">"), + ); }, }; } @@ -212,7 +227,11 @@ function resolveEdgesForParent( const resolvedChildNodeIds: string[] = []; for (const [dependencyName, range] of Object.entries(dependencyRanges)) { - const childPackagePath = resolveDependencyPackagePath(resolutionBasePaths, dependencyName, nodeIdByPackagePath); + const childPackagePath = resolveDependencyPackagePath( + resolutionBasePaths, + dependencyName, + nodeIdByPackagePath, + ); if (!childPackagePath) continue; const childNodeId = nodeIdByPackagePath.get(childPackagePath); @@ -220,18 +239,23 @@ function resolveEdgesForParent( resolvedChildNodeIds.push(childNodeId); - const parentNodeId = parentPackagePath ? nodeIdByPackagePath.get(parentPackagePath) : null; + const parentNodeId = parentPackagePath + ? nodeIdByPackagePath.get(parentPackagePath) + : null; if (!parentNodeId) continue; - const childSet = childNodeIdsByParentNodeId.get(parentNodeId) ?? new Set(); + const childSet = + childNodeIdsByParentNodeId.get(parentNodeId) ?? new Set(); childSet.add(childNodeId); childNodeIdsByParentNodeId.set(parentNodeId, childSet); - const parentSet = parentNodeIdsByChildNodeId.get(childNodeId) ?? new Set(); + const parentSet = + parentNodeIdsByChildNodeId.get(childNodeId) ?? new Set(); parentSet.add(parentNodeId); parentNodeIdsByChildNodeId.set(childNodeId, parentSet); - const rangesForParent = rangeByParentNodeId.get(parentNodeId) ?? new Map(); + const rangesForParent = + rangeByParentNodeId.get(parentNodeId) ?? new Map(); rangesForParent.set(dependencyName, range); rangeByParentNodeId.set(parentNodeId, rangesForParent); } @@ -239,14 +263,13 @@ function resolveEdgesForParent( return unique(resolvedChildNodeIds); } -function collectDependencyRanges(meta: RawLockPackage | null | undefined): Record { +function collectDependencyRanges( + meta: RawLockPackage | null | undefined, +): Record { if (!meta) return {}; const ranges: Record = {}; - for (const source of [ - meta.dependencies, - meta.optionalDependencies, - ]) { + for (const source of [meta.dependencies, meta.optionalDependencies]) { if (!source || typeof source !== "object") continue; for (const [name, range] of Object.entries(source)) { if (typeof range !== "string" || !range) continue; @@ -257,7 +280,9 @@ function collectDependencyRanges(meta: RawLockPackage | null | undefined): Recor return ranges; } -function collectRootDependencyRanges(meta: RawLockPackage | null | undefined): Record { +function collectRootDependencyRanges( + meta: RawLockPackage | null | undefined, +): Record { if (!meta) return {}; const ranges = collectDependencyRanges(meta); @@ -318,7 +343,11 @@ function packageNameFromPath(packagePath: string): string | null { return name || null; } -function rememberPath(pathSetByNodeId: Map>, nodeId: string, pathParts: string[]) { +function rememberPath( + pathSetByNodeId: Map>, + nodeId: string, + pathParts: string[], +) { const serialized = pathParts.join(">"); const existing = pathSetByNodeId.get(nodeId) ?? new Set(); existing.add(serialized); @@ -352,7 +381,10 @@ function resolveDependencySource( return linkedTarget ?? meta; } -function resolveBasePaths(packagePath: string, meta: RawLockPackage | null | undefined): string[] { +function resolveBasePaths( + packagePath: string, + meta: RawLockPackage | null | undefined, +): string[] { if (!meta?.link || !meta.resolved) { return [packagePath]; } diff --git a/src/parsers/package-json.ts b/src/parsers/package-json.ts index bdb6e948..fae46469 100644 --- a/src/parsers/package-json.ts +++ b/src/parsers/package-json.ts @@ -5,19 +5,31 @@ import { upsertPackage } from "./utils.js"; export function loadFromPackageJson( filePath: string, - prodOnly: boolean + prodOnly: boolean, ): { packages: PackageRef[]; skippedDependencies: string[] } { const raw = JSON.parse(fs.readFileSync(filePath, "utf8")); const map = new Map(); const skipped: string[] = []; - const sections: Array<{ name: string; deps: Record | undefined; dev: boolean }> = [ + const sections: Array<{ + name: string; + deps: Record | undefined; + dev: boolean; + }> = [ { name: "dependencies", deps: raw.dependencies, dev: false }, - { name: "optionalDependencies", deps: raw.optionalDependencies, dev: false } + { + name: "optionalDependencies", + deps: raw.optionalDependencies, + dev: false, + }, ]; if (!prodOnly) { - sections.push({ name: "devDependencies", deps: raw.devDependencies, dev: true }); + sections.push({ + name: "devDependencies", + deps: raw.devDependencies, + dev: true, + }); } for (const section of sections) { @@ -35,13 +47,13 @@ export function loadFromPackageJson( version: exactVersion, ecosystem: "npm", dev: section.dev, - paths: [["project", name]] + paths: [["project", name]], }); } } return { packages: [...map.values()], - skippedDependencies: skipped.slice(0, 50) + skippedDependencies: skipped.slice(0, 50), }; } diff --git a/src/parsers/package-lock.ts b/src/parsers/package-lock.ts index b60d0142..3881fd7e 100644 --- a/src/parsers/package-lock.ts +++ b/src/parsers/package-lock.ts @@ -14,7 +14,11 @@ export function buildNpmWorkspaceMap(filePath: string): Map { const workspacePath = pkgPath === "" ? "." : pkgPath; - for (const depSectionName of ["dependencies", "optionalDependencies", "devDependencies"]) { + for (const depSectionName of [ + "dependencies", + "optionalDependencies", + "devDependencies", + ]) { const depSection = meta?.[depSectionName]; if (!depSection || typeof depSection !== "object") continue; for (const depName of Object.keys(depSection)) { @@ -29,7 +33,10 @@ export function buildNpmWorkspaceMap(filePath: string): Map { return map; } -export function loadFromPackageLock(filePath: string, prodOnly: boolean): PackageRef[] { +export function loadFromPackageLock( + filePath: string, + prodOnly: boolean, +): PackageRef[] { const raw = JSON.parse(fs.readFileSync(filePath, "utf8")); const map = new Map(); @@ -40,7 +47,9 @@ export function loadFromPackageLock(filePath: string, prodOnly: boolean): Packag if (!pkgPath || pkgPath === "") continue; if (!pkgPath.includes("node_modules/")) continue; - const name = pkgPath.slice(pkgPath.lastIndexOf("node_modules/") + "node_modules/".length); + const name = pkgPath.slice( + pkgPath.lastIndexOf("node_modules/") + "node_modules/".length, + ); const version = meta?.version; const dev = !!meta?.dev; @@ -48,14 +57,28 @@ export function loadFromPackageLock(filePath: string, prodOnly: boolean): Packag if (prodOnly && dev) continue; const nodeIds = graph.nodeIdsFor(name, version); - const graphPaths = nodeIds.flatMap(id => graph.pathsFor(id)); - const paths = graphPaths.length > 0 ? graphPaths : [normalizeNodeModulesPath(pkgPath)]; + const graphPaths = nodeIds.flatMap((id) => graph.pathsFor(id)); + const paths = + graphPaths.length > 0 + ? graphPaths + : [normalizeNodeModulesPath(pkgPath)]; const resolvedUrl = meta?.resolved as string | undefined; - upsertPackage(map, { name, version, ecosystem: "npm", dev, paths, resolvedUrl }); + upsertPackage(map, { + name, + version, + ecosystem: "npm", + dev, + paths, + resolvedUrl, + }); } } - if (map.size === 0 && raw.dependencies && typeof raw.dependencies === "object") { + if ( + map.size === 0 && + raw.dependencies && + typeof raw.dependencies === "object" + ) { walkLegacyDeps(raw.dependencies, map, prodOnly, ["project"]); } @@ -66,7 +89,7 @@ function walkLegacyDeps( deps: Record, map: Map, prodOnly: boolean, - currentPath: string[] + currentPath: string[], ) { for (const [name, meta] of Object.entries(deps)) { const version = meta?.version; @@ -74,7 +97,13 @@ function walkLegacyDeps( const nextPath = [...currentPath, name]; if (name && version && !(prodOnly && dev)) { - upsertPackage(map, { name, version, ecosystem: "npm", dev, paths: [nextPath] }); + upsertPackage(map, { + name, + version, + ecosystem: "npm", + dev, + paths: [nextPath], + }); } if (meta?.dependencies) { diff --git a/src/parsers/pnpm-lock.ts b/src/parsers/pnpm-lock.ts index 52239547..7bbaa5ce 100644 --- a/src/parsers/pnpm-lock.ts +++ b/src/parsers/pnpm-lock.ts @@ -15,7 +15,11 @@ export function buildPnpmWorkspaceMap(filePath: string): Map { const map = new Map(); for (const [importerPath, importer] of Object.entries(importers)) { - for (const depSectionName of ["dependencies", "optionalDependencies", "devDependencies"]) { + for (const depSectionName of [ + "dependencies", + "optionalDependencies", + "devDependencies", + ]) { const depSection = importer?.[depSectionName]; if (!depSection || typeof depSection !== "object") continue; for (const depName of Object.keys(depSection)) { @@ -30,11 +34,16 @@ export function buildPnpmWorkspaceMap(filePath: string): Map { return map; } -export function loadFromPnpmLock(filePath: string, prodOnly: boolean): PackageRef[] { +export function loadFromPnpmLock( + filePath: string, + prodOnly: boolean, +): PackageRef[] { const content = fs.readFileSync(filePath, "utf8"); const parsed = YAML.parse(content) as any; const majorVersion = parseInt(String(parsed?.lockfileVersion ?? "0"), 10); - return majorVersion >= 9 ? loadV9(parsed, prodOnly) : loadLegacy(parsed, prodOnly); + return majorVersion >= 9 + ? loadV9(parsed, prodOnly) + : loadLegacy(parsed, prodOnly); } function loadLegacy(parsed: any, prodOnly: boolean): PackageRef[] { @@ -60,12 +69,23 @@ function loadLegacy(parsed: any, prodOnly: boolean): PackageRef[] { const dev = !!meta?.dev; if (prodOnly && dev) continue; const resolvedUrl = meta?.resolution?.tarball as string | undefined; - upsertPackage(map, { name: ref.name, version: ref.version, ecosystem: "npm", dev, paths: [], resolvedUrl }); + upsertPackage(map, { + name: ref.name, + version: ref.version, + ecosystem: "npm", + dev, + paths: [], + resolvedUrl, + }); } const rootDeps: string[] = []; for (const importer of Object.values(importers)) { - for (const depSectionName of ["dependencies", "optionalDependencies", "devDependencies"]) { + for (const depSectionName of [ + "dependencies", + "optionalDependencies", + "devDependencies", + ]) { if (prodOnly && depSectionName == "devDependencies") continue; const depSection = importer?.[depSectionName]; if (!depSection || typeof depSection !== "object") continue; @@ -80,7 +100,7 @@ function loadLegacy(parsed: any, prodOnly: boolean): PackageRef[] { name: String(depName), version: fallbackVersion, ecosystem: "npm", - paths: [["project", String(depName)]] + paths: [["project", String(depName)]], }); } } @@ -124,13 +144,24 @@ function loadV9(parsed: any, prodOnly: boolean): PackageRef[] { graph.set(ref.key, [...depKeys]); const dev = !!meta?.dev; if (prodOnly && dev) continue; - upsertPackage(map, { name: ref.name, version: ref.version, ecosystem: "npm", dev, paths: [], resolvedUrl: resolutionUrls.get(ref.key) }); + upsertPackage(map, { + name: ref.name, + version: ref.version, + ecosystem: "npm", + dev, + paths: [], + resolvedUrl: resolutionUrls.get(ref.key), + }); } const rootDeps: string[] = []; const devDepNames = new Set(); for (const importer of Object.values(importers)) { - for (const depSectionName of ["dependencies", "optionalDependencies", "devDependencies"]) { + for (const depSectionName of [ + "dependencies", + "optionalDependencies", + "devDependencies", + ]) { if (prodOnly && depSectionName === "devDependencies") continue; const depSection = importer?.[depSectionName]; if (!depSection || typeof depSection !== "object") continue; @@ -148,7 +179,7 @@ function loadV9(parsed: any, prodOnly: boolean): PackageRef[] { name: String(depName), version: fallbackVersion, ecosystem: "npm", - paths: [["project", String(depName)]] + paths: [["project", String(depName)]], }); } } @@ -166,9 +197,14 @@ function collectPnpmPaths( rootDeps: string[], graph: Map, map: Map, - parsePackageKey: (key: string) => { key: string; name: string; version: string } | null, + parsePackageKey: ( + key: string, + ) => { key: string; name: string; version: string } | null, ): void { - const queue = rootDeps.map(dep => ({ key: dep, path: ["project"] as string[] })); + const queue = rootDeps.map((dep) => ({ + key: dep, + path: ["project"] as string[], + })); const visitedStates = new Set(); let head = 0; @@ -185,11 +221,17 @@ function collectPnpmPaths( const pkgKey = `${ref.name}@${ref.version}`; const pkg = map.get(pkgKey); if (pkg) { - pkg.paths = uniquePathArrays([...(pkg.paths ?? []), nextPath]).slice(0, MAX_PATHS_PER_PACKAGE); + pkg.paths = uniquePathArrays([...(pkg.paths ?? []), nextPath]).slice( + 0, + MAX_PATHS_PER_PACKAGE, + ); } if (nextPath.length >= MAX_PATH_DEPTH) continue; - if ((pkg?.paths?.length ?? 0) >= MAX_PATHS_PER_PACKAGE && !(pkg?.paths ?? []).some(path => pathsEqual(path, nextPath))) { + if ( + (pkg?.paths?.length ?? 0) >= MAX_PATHS_PER_PACKAGE && + !(pkg?.paths ?? []).some((path) => pathsEqual(path, nextPath)) + ) { continue; } @@ -205,7 +247,9 @@ function pathsEqual(left: string[], right: string[]): boolean { return left.every((value, index) => value === right[index]); } -function parsePnpmPackageKey(key: string): { key: string; name: string; version: string } | null { +function parsePnpmPackageKey( + key: string, +): { key: string; name: string; version: string } | null { const cleaned = key.replace(/^\//, "").split("(")[0]; const match = cleaned.match(/^(@?[^/]+(?:\/[^/]+)?)\/([^/]+)$/); if (!match) return null; @@ -213,7 +257,9 @@ function parsePnpmPackageKey(key: string): { key: string; name: string; version: return { key, name, version }; } -function parsePnpmPackageKeyV9(key: string): { key: string; name: string; version: string } | null { +function parsePnpmPackageKeyV9( + key: string, +): { key: string; name: string; version: string } | null { const cleaned = key.split("(")[0]; // strip peer-dep suffix e.g. handlebars@4.7.8(foo@1.0.0) const idx = cleaned.lastIndexOf("@"); if (idx <= 0) return null; // no @ or @ is the first char @@ -225,8 +271,12 @@ function parsePnpmPackageKeyV9(key: string): { key: string; name: string; versio function normalizePnpmDepRef(depName: string, depRef: unknown): string | null { if (typeof depRef === "string") { - const cleaned = depRef.replace(/^link:/, "").replace(/^workspace:/, "").split("(")[0]; - if (!cleaned || cleaned.startsWith(".") || cleaned.startsWith("..")) return null; + const cleaned = depRef + .replace(/^link:/, "") + .replace(/^workspace:/, "") + .split("(")[0]; + if (!cleaned || cleaned.startsWith(".") || cleaned.startsWith("..")) + return null; if (cleaned.startsWith("/")) return cleaned; if (cleaned.includes("/")) return "/" + cleaned.replace(/^\//, ""); if (looksLikeVersion(cleaned)) return `/${depName}/${cleaned}`; @@ -240,10 +290,17 @@ function normalizePnpmDepRef(depName: string, depRef: unknown): string | null { return null; } -function normalizePnpmDepRefV9(depName: string, depRef: unknown): string | null { +function normalizePnpmDepRefV9( + depName: string, + depRef: unknown, +): string | null { if (typeof depRef === "string") { - const cleaned = depRef.replace(/^link:/, "").replace(/^workspace:/, "").split("(")[0]; - if (!cleaned || cleaned.startsWith(".") || cleaned.startsWith("..")) return null; + const cleaned = depRef + .replace(/^link:/, "") + .replace(/^workspace:/, "") + .split("(")[0]; + if (!cleaned || cleaned.startsWith(".") || cleaned.startsWith("..")) + return null; if (looksLikeVersion(cleaned)) return `${depName}@${cleaned}`; // Handle aliased deps where the ref is a full "pkgName@version" key (e.g. '@remix-run/dev': '@vercel/remix-run-dev@1.16.1') const atIdx = cleaned.lastIndexOf("@"); diff --git a/src/parsers/utils.ts b/src/parsers/utils.ts index fb49f4d9..866b5d28 100644 --- a/src/parsers/utils.ts +++ b/src/parsers/utils.ts @@ -1,7 +1,10 @@ import type { PackageRef } from "../types.js"; import { uniquePathArrays } from "../utils/array.js"; -export function markDevPackages(map: Map, devDepNames: Set): void { +export function markDevPackages( + map: Map, + devDepNames: Set, +): void { if (devDepNames.size === 0) return; for (const pkg of map.values()) { @@ -9,24 +12,35 @@ export function markDevPackages(map: Map, devDepNames: Set p.length >= 2 && devDepNames.has(p[1])); + const allPathsFromDev = paths.every( + (p) => p.length >= 2 && devDepNames.has(p[1]), + ); if (allPathsFromDev) { pkg.dev = true; } } } -export function upsertPackage(map: Map, candidate: PackageRef) { +export function upsertPackage( + map: Map, + candidate: PackageRef, +) { const key = `${candidate.name}@${candidate.version}`; const existing = map.get(key); if (!existing) { - map.set(key, { ...candidate, paths: uniquePathArrays(candidate.paths ?? []).slice(0, 5) }); + map.set(key, { + ...candidate, + paths: uniquePathArrays(candidate.paths ?? []).slice(0, 5), + }); return; } existing.dev = existing.dev && candidate.dev; - existing.paths = uniquePathArrays([...(existing.paths ?? []), ...(candidate.paths ?? [])]).slice(0, 5); + existing.paths = uniquePathArrays([ + ...(existing.paths ?? []), + ...(candidate.paths ?? []), + ]).slice(0, 5); } export function normalizeNodeModulesPath(pkgPath: string): string[] { diff --git a/src/parsers/yarn-lock.ts b/src/parsers/yarn-lock.ts index a28020b0..c8cb1a2b 100644 --- a/src/parsers/yarn-lock.ts +++ b/src/parsers/yarn-lock.ts @@ -15,14 +15,16 @@ export function buildYarnWorkspaceMap(filePath: string): Map { let rootPkg: any; try { - rootPkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")); + rootPkg = JSON.parse( + fs.readFileSync(path.join(dir, "package.json"), "utf8"), + ); } catch { return map; } const patterns: string[] = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces - : rootPkg.workspaces?.packages ?? []; + : (rootPkg.workspaces?.packages ?? []); if (!patterns.length) return map; @@ -35,9 +37,10 @@ export function buildYarnWorkspaceMap(filePath: string): Map { if (lastPart === "*") { const baseDir = path.join(dir, ...baseParts); try { - wsDirs = fs.readdirSync(baseDir, { withFileTypes: true }) - .filter(d => d.isDirectory()) - .map(d => [...baseParts, d.name].join("/")); + wsDirs = fs + .readdirSync(baseDir, { withFileTypes: true }) + .filter((d) => d.isDirectory()) + .map((d) => [...baseParts, d.name].join("/")); } catch { continue; } @@ -48,19 +51,26 @@ export function buildYarnWorkspaceMap(filePath: string): Map { for (const wsDir of wsDirs) { let wsPkg: any; try { - wsPkg = JSON.parse(fs.readFileSync(path.join(dir, wsDir, "package.json"), "utf8")); + wsPkg = JSON.parse( + fs.readFileSync(path.join(dir, wsDir, "package.json"), "utf8"), + ); } catch { continue; } const wsName: string = wsPkg.name; if (!wsName) continue; - for (const depSection of ["dependencies", "optionalDependencies", "devDependencies"]) { + for (const depSection of [ + "dependencies", + "optionalDependencies", + "devDependencies", + ]) { const deps = wsPkg[depSection]; if (!deps || typeof deps !== "object") continue; for (const depName of Object.keys(deps)) { const existing = map.get(depName) ?? []; - if (!existing.includes(wsName)) map.set(depName, [...existing, wsName]); + if (!existing.includes(wsName)) + map.set(depName, [...existing, wsName]); } } } @@ -70,7 +80,7 @@ export function buildYarnWorkspaceMap(filePath: string): Map { } function isYarnBerry(content: string): boolean { - return content.startsWith('__metadata:') || content.includes('\n__metadata:'); + return content.startsWith("__metadata:") || content.includes("\n__metadata:"); } function pathsEqual(left: string[], right: string[]): boolean { @@ -78,7 +88,9 @@ function pathsEqual(left: string[], right: string[]): boolean { return left.every((value, index) => value === right[index]); } -function parseYarnPackageKey(key: string): { key: string; name: string; version: string } | null { +function parseYarnPackageKey( + key: string, +): { key: string; name: string; version: string } | null { const idx = key.lastIndexOf("@"); if (idx <= 0) return null; const name = key.slice(0, idx); @@ -92,7 +104,10 @@ function collectYarnPaths( graph: Map, map: Map, ): void { - const queue = rootDepKeys.map(key => ({ key, path: ["project"] as string[] })); + const queue = rootDepKeys.map((key) => ({ + key, + path: ["project"] as string[], + })); const visitedStates = new Set(); let head = 0; @@ -108,11 +123,17 @@ function collectYarnPaths( const pkg = map.get(ref.key); if (pkg) { - pkg.paths = uniquePathArrays([...(pkg.paths ?? []), nextPath]).slice(0, MAX_PATHS_PER_PACKAGE); + pkg.paths = uniquePathArrays([...(pkg.paths ?? []), nextPath]).slice( + 0, + MAX_PATHS_PER_PACKAGE, + ); } if (nextPath.length >= MAX_PATH_DEPTH) continue; - if ((pkg?.paths?.length ?? 0) >= MAX_PATHS_PER_PACKAGE && !(pkg?.paths ?? []).some(path => pathsEqual(path, nextPath))) { + if ( + (pkg?.paths?.length ?? 0) >= MAX_PATHS_PER_PACKAGE && + !(pkg?.paths ?? []).some((path) => pathsEqual(path, nextPath)) + ) { continue; } @@ -130,14 +151,22 @@ function loadRootDepKeys( const dir = path.dirname(filePath); let rootPkg: any; try { - rootPkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")); + rootPkg = JSON.parse( + fs.readFileSync(path.join(dir, "package.json"), "utf8"), + ); } catch { return { keys: [], devDepNames: new Set() }; } - const devDepNames = new Set(Object.keys(rootPkg.devDependencies ?? {})); + const devDepNames = new Set( + Object.keys(rootPkg.devDependencies ?? {}), + ); const rootDepKeys: string[] = []; - for (const depSection of ["dependencies", "optionalDependencies", "devDependencies"]) { + for (const depSection of [ + "dependencies", + "optionalDependencies", + "devDependencies", + ]) { const deps = rootPkg[depSection]; if (!deps || typeof deps !== "object") continue; for (const [depName, depRange] of Object.entries(deps)) { @@ -183,31 +212,41 @@ function parseBerryDependencies(block: string): Record { continue; } - dependencies[depMatch[1].trim().replace(/^"|"$/g, "")] = depMatch[2].trim().replace(/^"|"$/g, ""); + dependencies[depMatch[1].trim().replace(/^"|"$/g, "")] = depMatch[2] + .trim() + .replace(/^"|"$/g, ""); } return dependencies; } -function loadFromYarnBerryLock(content: string, filePath: string): PackageRef[] { +function loadFromYarnBerryLock( + content: string, + filePath: string, +): PackageRef[] { const map = new Map(); const graph = new Map(); const selectorToKey = new Map(); const blocks = content.split(/\n\n+/); for (const block of blocks) { - const lines = block.trim().split('\n'); - if (!lines[0] || lines[0].startsWith('__metadata')) continue; - - const selectorLine = lines[0].trim().replace(/:$/, "").replace(/^"|"$/g, ""); - const resolutionLine = lines.find((l) => l.trim().startsWith('resolution:')); + const lines = block.trim().split("\n"); + if (!lines[0] || lines[0].startsWith("__metadata")) continue; + + const selectorLine = lines[0] + .trim() + .replace(/:$/, "") + .replace(/^"|"$/g, ""); + const resolutionLine = lines.find((l) => + l.trim().startsWith("resolution:"), + ); if (!resolutionLine) continue; const resMatch = resolutionLine.match(/resolution:\s+"(.+)"/); if (!resMatch) continue; const resolution = resMatch[1]; - const npmIdx = resolution.lastIndexOf('@npm:'); + const npmIdx = resolution.lastIndexOf("@npm:"); if (npmIdx < 0) continue; const name = resolution.slice(0, npmIdx); @@ -215,25 +254,39 @@ function loadFromYarnBerryLock(content: string, filePath: string): PackageRef[] if (!name || !version) continue; const canonicalKey = `${name}@${version}`; - for (const selector of selectorLine.split(",").map(part => part.trim()).filter(Boolean)) { + for (const selector of selectorLine + .split(",") + .map((part) => part.trim()) + .filter(Boolean)) { selectorToKey.set(selector, canonicalKey); } - upsertPackage(map, { name, version, ecosystem: 'npm', paths: [] }); + upsertPackage(map, { name, version, ecosystem: "npm", paths: [] }); } for (const block of blocks) { - const lines = block.trim().split('\n'); - if (!lines[0] || lines[0].startsWith('__metadata')) continue; - - const selectorLine = lines[0].trim().replace(/:$/, "").replace(/^"|"$/g, ""); - const canonicalKey = selectorToKey.get( - selectorLine.split(",").map(part => part.trim()).find(part => selectorToKey.has(part)) ?? selectorLine, - ) ?? selectorToKey.get(selectorLine); + const lines = block.trim().split("\n"); + if (!lines[0] || lines[0].startsWith("__metadata")) continue; + + const selectorLine = lines[0] + .trim() + .replace(/:$/, "") + .replace(/^"|"$/g, ""); + const canonicalKey = + selectorToKey.get( + selectorLine + .split(",") + .map((part) => part.trim()) + .find((part) => selectorToKey.has(part)) ?? selectorLine, + ) ?? selectorToKey.get(selectorLine); if (!canonicalKey) continue; const depKeys = new Set(); - for (const [depName, depRef] of Object.entries(parseBerryDependencies(block))) { - const childSelector = depRef.startsWith("npm:") ? `${depName}@${depRef}` : `${depName}@${depRef}`; + for (const [depName, depRef] of Object.entries( + parseBerryDependencies(block), + )) { + const childSelector = depRef.startsWith("npm:") + ? `${depName}@${depRef}` + : `${depName}@${depRef}`; const childKey = selectorToKey.get(childSelector); if (childKey) depKeys.add(childKey); } @@ -241,7 +294,11 @@ function loadFromYarnBerryLock(content: string, filePath: string): PackageRef[] graph.set(canonicalKey, [...depKeys]); } - const { keys: rootDepKeys, devDepNames } = loadRootDepKeys(filePath, selectorToKey, map); + const { keys: rootDepKeys, devDepNames } = loadRootDepKeys( + filePath, + selectorToKey, + map, + ); collectYarnPaths(rootDepKeys, graph, map); markDevPackages(map, devDepNames); @@ -257,7 +314,10 @@ function loadFromYarnClassicLock(parsed: any, filePath: string): PackageRef[] { const version = meta?.version; if (!version) continue; - const selectors = String(selectorKey).split(",").map(selector => selector.trim()).filter(Boolean); + const selectors = String(selectorKey) + .split(",") + .map((selector) => selector.trim()) + .filter(Boolean); const firstSelector = selectors[0]; const atIndex = firstSelector.lastIndexOf("@"); if (atIndex <= 0) continue; @@ -269,14 +329,23 @@ function loadFromYarnClassicLock(parsed: any, filePath: string): PackageRef[] { selectorToKey.set(selector, canonicalKey); } - upsertPackage(map, { name, version, ecosystem: "npm", paths: [], resolvedUrl: meta?.resolved as string | undefined }); + upsertPackage(map, { + name, + version, + ecosystem: "npm", + paths: [], + resolvedUrl: meta?.resolved as string | undefined, + }); } for (const [selectorKey, meta] of Object.entries(parsed.object)) { const version = meta?.version; if (!version) continue; - const selectors = String(selectorKey).split(",").map(selector => selector.trim()).filter(Boolean); + const selectors = String(selectorKey) + .split(",") + .map((selector) => selector.trim()) + .filter(Boolean); const firstSelector = selectors[0]; const atIndex = firstSelector.lastIndexOf("@"); if (atIndex <= 0) continue; @@ -296,7 +365,11 @@ function loadFromYarnClassicLock(parsed: any, filePath: string): PackageRef[] { graph.set(canonicalKey, [...depKeys]); } - const { keys: rootDepKeys, devDepNames } = loadRootDepKeys(filePath, selectorToKey, map); + const { keys: rootDepKeys, devDepNames } = loadRootDepKeys( + filePath, + selectorToKey, + map, + ); collectYarnPaths(rootDepKeys, graph, map); markDevPackages(map, devDepNames); diff --git a/src/remediation/fix-commands.ts b/src/remediation/fix-commands.ts index 07d6cdfe..94ecbe02 100644 --- a/src/remediation/fix-commands.ts +++ b/src/remediation/fix-commands.ts @@ -2,7 +2,10 @@ import type { Finding, ScanInput, SeverityLabel, ChainHop } from "../types.js"; import { severityOrder } from "../constants.js"; import { compareVersions, looksLikeVersion } from "../utils/version.js"; import { getPrimaryParent } from "../utils/finding.js"; -import { calculatePathCoverage, formatDependencyPath } from "../utils/path-coverage.js"; +import { + calculatePathCoverage, + formatDependencyPath, +} from "../utils/path-coverage.js"; import { pluralize } from "../utils/string.js"; import { buildNpmWorkspaceMap } from "../parsers/package-lock.js"; import { buildPnpmWorkspaceMap } from "../parsers/pnpm-lock.js"; @@ -49,7 +52,13 @@ export type SuggestedFixCommandPlan = { command: string | null; sections: Array<{ key: string; - kind: "urgent" | "direct" | "direct-adjusted" | "parent-upgrade" | "parent-update" | "validated-chain-upgrade"; + kind: + | "urgent" + | "direct" + | "direct-adjusted" + | "parent-upgrade" + | "parent-update" + | "validated-chain-upgrade"; severity: SeverityLabel; title: string; command: string; @@ -73,11 +82,21 @@ export function buildSuggestedFixCommandPlan( let workspaceMap: Map = new Map(); if (scanInput.filePath) { try { - if (packageManager === "npm" && (scanInput.source === "package-lock" || scanInput.source === "npm-shrinkwrap")) { + if ( + packageManager === "npm" && + (scanInput.source === "package-lock" || + scanInput.source === "npm-shrinkwrap") + ) { workspaceMap = buildNpmWorkspaceMap(scanInput.filePath); - } else if (packageManager === "pnpm" && scanInput.source === "pnpm-lock") { + } else if ( + packageManager === "pnpm" && + scanInput.source === "pnpm-lock" + ) { workspaceMap = buildPnpmWorkspaceMap(scanInput.filePath); - } else if (packageManager === "yarn" && scanInput.source === "yarn-lock") { + } else if ( + packageManager === "yarn" && + scanInput.source === "yarn-lock" + ) { workspaceMap = buildYarnWorkspaceMap(scanInput.filePath); } else if (packageManager === "bun" && scanInput.source === "bun-lock") { workspaceMap = buildBunWorkspaceMap(scanInput.filePath); @@ -93,16 +112,18 @@ export function buildSuggestedFixCommandPlan( } const prioritizedFindings = [...findings] - .filter(f => f.severity === "critical" || f.severity === "high") + .filter((f) => f.severity === "critical" || f.severity === "high") .sort((a, b) => { const sevDelta = severityOrder[b.severity] - severityOrder[a.severity]; if (sevDelta !== 0) return sevDelta; - const usageScore = (finding: Finding) => finding.usage?.imported ? 1 : 0; + const usageScore = (finding: Finding) => + finding.usage?.imported ? 1 : 0; const usageDelta = usageScore(b) - usageScore(a); if (usageDelta !== 0) return usageDelta; - const relScore = (finding: Finding) => finding.relationship === "direct" ? 1 : 0; + const relScore = (finding: Finding) => + finding.relationship === "direct" ? 1 : 0; const relDelta = relScore(b) - relScore(a); if (relDelta !== 0) return relDelta; @@ -115,16 +136,18 @@ export function buildSuggestedFixCommandPlan( const orderedFindings = [ ...prioritizedFindings, ...findings - .filter(f => f.severity !== "critical" && f.severity !== "high") + .filter((f) => f.severity !== "critical" && f.severity !== "high") .sort((a, b) => { const sevDelta = severityOrder[b.severity] - severityOrder[a.severity]; if (sevDelta !== 0) return sevDelta; - const usageScore = (finding: Finding) => finding.usage?.imported ? 1 : 0; + const usageScore = (finding: Finding) => + finding.usage?.imported ? 1 : 0; const usageDelta = usageScore(b) - usageScore(a); if (usageDelta !== 0) return usageDelta; - const relScore = (finding: Finding) => finding.relationship === "direct" ? 1 : 0; + const relScore = (finding: Finding) => + finding.relationship === "direct" ? 1 : 0; const relDelta = relScore(b) - relScore(a); if (relDelta !== 0) return relDelta; @@ -133,20 +156,29 @@ export function buildSuggestedFixCommandPlan( ]; for (const finding of orderedFindings) { - const urgent = finding.severity === "critical" || finding.severity === "high"; + const urgent = + finding.severity === "critical" || finding.severity === "high"; if (finding.relationship === "direct") { - const hasValidatedField = Object.prototype.hasOwnProperty.call(finding, "validatedFirstFixedVersion"); - const directTarget = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + const hasValidatedField = Object.prototype.hasOwnProperty.call( + finding, + "validatedFirstFixedVersion", + ); + const directTarget = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; // In offline mode validateDirectFixTargets never runs, so an unset // validatedFirstFixedVersion is "did not validate" rather than "validation // failed" — fall back to the advisory hint instead of dropping the target. - const validatedFieldOk = offline || !hasValidatedField || finding.validatedFirstFixedVersion !== null; + const validatedFieldOk = + offline || + !hasValidatedField || + finding.validatedFirstFixedVersion !== null; const allVersionsVulnerable = finding.validatedTargetScannedVersions != null && finding.validatedTargetKnownVulnerableVersions != null && finding.validatedTargetScannedVersions > 0 && - finding.validatedTargetKnownVulnerableVersions >= finding.validatedTargetScannedVersions; + finding.validatedTargetKnownVulnerableVersions >= + finding.validatedTargetScannedVersions; if (directTarget && allVersionsVulnerable) { skippedByKey.set(`direct:${finding.pkg.name}@${finding.pkg.version}`, { package: finding.pkg.name, @@ -161,13 +193,15 @@ export function buildSuggestedFixCommandPlan( isUpgradeTarget(finding.pkg.version, directTarget) && validatedFieldOk ) { - const pkgWorkspaces = workspaceMap.get(finding.pkg.name)?.filter(w => w !== ".") ?? []; + const pkgWorkspaces = + workspaceMap.get(finding.pkg.name)?.filter((w) => w !== ".") ?? []; upsertTarget(targetsByPackage, { package: finding.pkg.name, currentVersion: finding.pkg.version, targetVersion: directTarget, scannedVersions: finding.validatedTargetScannedVersions ?? null, - knownVulnerableVersions: finding.validatedTargetKnownVulnerableVersions ?? null, + knownVulnerableVersions: + finding.validatedTargetKnownVulnerableVersions ?? null, kind: "direct", urgent, severity: finding.severity, @@ -188,28 +222,37 @@ export function buildSuggestedFixCommandPlan( isDev: finding.pkg.dev ?? false, }); } else if (finding.fixVersionValidationNote) { - skippedByKey.set(`${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, { - package: finding.pkg.name, - version: finding.pkg.version, - relationship: finding.relationship, - reason: finding.fixVersionValidationNote, - }); + skippedByKey.set( + `${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, + { + package: finding.pkg.name, + version: finding.pkg.version, + relationship: finding.relationship, + reason: finding.fixVersionValidationNote, + }, + ); } else if (finding.firstFixedVersion) { - skippedByKey.set(`${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, { - package: finding.pkg.name, - version: finding.pkg.version, - relationship: finding.relationship, - reason: `Fixed-version hint ${directTarget} is not an upgrade from installed ${finding.pkg.version}.`, - }); + skippedByKey.set( + `${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, + { + package: finding.pkg.name, + version: finding.pkg.version, + relationship: finding.relationship, + reason: `Fixed-version hint ${directTarget} is not an upgrade from installed ${finding.pkg.version}.`, + }, + ); } else { - skippedByKey.set(`${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, { - package: finding.pkg.name, - version: finding.pkg.version, - relationship: finding.relationship, - reason: urgent - ? "No safe upgrade target is known for this urgent direct dependency." - : "No safe upgrade target is known for this direct dependency.", - }); + skippedByKey.set( + `${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, + { + package: finding.pkg.name, + version: finding.pkg.version, + relationship: finding.relationship, + reason: urgent + ? "No safe upgrade target is known for this urgent direct dependency." + : "No safe upgrade target is known for this direct dependency.", + }, + ); } continue; } @@ -227,12 +270,15 @@ export function buildSuggestedFixCommandPlan( if ( packageManager === "npm" && finding.relationship === "transitive" && - finding.recommendedNpmTransitiveRemediation?.kind === "update-parent-within-range" + finding.recommendedNpmTransitiveRemediation?.kind === + "update-parent-within-range" ) { upsertTarget(targetsByPackage, { package: finding.recommendedNpmTransitiveRemediation.package, - currentVersion: finding.recommendedNpmTransitiveRemediation.currentVersion, - targetVersion: finding.recommendedNpmTransitiveRemediation.targetChildVersion, + currentVersion: + finding.recommendedNpmTransitiveRemediation.currentVersion, + targetVersion: + finding.recommendedNpmTransitiveRemediation.targetChildVersion, displayTargetVersion: "lockfile refresh", scannedVersions: null, knownVulnerableVersions: null, @@ -242,9 +288,15 @@ export function buildSuggestedFixCommandPlan( adjusted: false, adjustmentNote: null, reason: finding.recommendedNpmTransitiveRemediation.reason, - command: buildNpmUpdateCommand(finding.recommendedNpmTransitiveRemediation.package, finding.recommendedNpmTransitiveRemediation.workspaces), + command: buildNpmUpdateCommand( + finding.recommendedNpmTransitiveRemediation.package, + finding.recommendedNpmTransitiveRemediation.workspaces, + ), usage: finding.usage ?? null, - isDev: devLookup.get(`${finding.recommendedNpmTransitiveRemediation.package}@${finding.recommendedNpmTransitiveRemediation.currentVersion}`) ?? false, + isDev: + devLookup.get( + `${finding.recommendedNpmTransitiveRemediation.package}@${finding.recommendedNpmTransitiveRemediation.currentVersion}`, + ) ?? false, }); continue; } @@ -252,7 +304,8 @@ export function buildSuggestedFixCommandPlan( if ( packageManager === "npm" && finding.relationship === "transitive" && - finding.recommendedNpmTransitiveRemediation?.kind === "upgrade-parent-to-version" && + finding.recommendedNpmTransitiveRemediation?.kind === + "upgrade-parent-to-version" && finding.recommendedNpmTransitiveRemediation.targetVersion && isUpgradeTarget( finding.recommendedNpmTransitiveRemediation.currentVersion, @@ -263,13 +316,17 @@ export function buildSuggestedFixCommandPlan( finding.dependencyPaths, finding.recommendedNpmTransitiveRemediation.viaPath, ); - const chainForNpmRemediation = finding.chainResolution?.directDep === finding.recommendedNpmTransitiveRemediation.package - ? finding.chainResolution - : null; + const chainForNpmRemediation = + finding.chainResolution?.directDep === + finding.recommendedNpmTransitiveRemediation.package + ? finding.chainResolution + : null; upsertTarget(targetsByPackage, { package: finding.recommendedNpmTransitiveRemediation.package, - currentVersion: finding.recommendedNpmTransitiveRemediation.currentVersion, - targetVersion: finding.recommendedNpmTransitiveRemediation.targetVersion, + currentVersion: + finding.recommendedNpmTransitiveRemediation.currentVersion, + targetVersion: + finding.recommendedNpmTransitiveRemediation.targetVersion, scannedVersions: null, knownVulnerableVersions: null, kind: "parent-upgrade", @@ -282,10 +339,15 @@ export function buildSuggestedFixCommandPlan( coveredPaths: coverage.coveredPaths, remainingPaths: coverage.remainingPaths, usage: finding.usage ?? null, - isDev: devLookup.get(`${finding.recommendedNpmTransitiveRemediation.package}@${finding.recommendedNpmTransitiveRemediation.currentVersion}`) ?? false, + isDev: + devLookup.get( + `${finding.recommendedNpmTransitiveRemediation.package}@${finding.recommendedNpmTransitiveRemediation.currentVersion}`, + ) ?? false, chainProof: chainForNpmRemediation?.chain, chainSafeVersion: chainForNpmRemediation?.safeVersion, - chainVulnerablePackage: chainForNpmRemediation ? finding.pkg.name : undefined, + chainVulnerablePackage: chainForNpmRemediation + ? finding.pkg.name + : undefined, }); continue; } @@ -302,9 +364,11 @@ export function buildSuggestedFixCommandPlan( finding.dependencyPaths, finding.recommendedParentUpgrade.viaPath, ); - const chainForParentUpgrade = finding.chainResolution?.directDep === finding.recommendedParentUpgrade.package - ? finding.chainResolution - : null; + const chainForParentUpgrade = + finding.chainResolution?.directDep === + finding.recommendedParentUpgrade.package + ? finding.chainResolution + : null; upsertTarget(targetsByPackage, { package: finding.recommendedParentUpgrade.package, currentVersion: finding.recommendedParentUpgrade.currentVersion, @@ -321,39 +385,55 @@ export function buildSuggestedFixCommandPlan( coveredPaths: coverage.coveredPaths, remainingPaths: coverage.remainingPaths, usage: finding.usage ?? null, - isDev: devLookup.get(`${finding.recommendedParentUpgrade.package}@${finding.recommendedParentUpgrade.currentVersion}`) ?? false, + isDev: + devLookup.get( + `${finding.recommendedParentUpgrade.package}@${finding.recommendedParentUpgrade.currentVersion}`, + ) ?? false, chainProof: chainForParentUpgrade?.chain, chainSafeVersion: chainForParentUpgrade?.safeVersion, - chainVulnerablePackage: chainForParentUpgrade ? finding.pkg.name : undefined, + chainVulnerablePackage: chainForParentUpgrade + ? finding.pkg.name + : undefined, }); continue; } - if (finding.relationship === "transitive" && finding.recommendedParentUpgrade) { - skippedByKey.set(`${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, { - package: finding.pkg.name, - version: finding.pkg.version, - relationship: finding.relationship, - reason: - `Suggested parent target ${finding.recommendedParentUpgrade.package}@${finding.recommendedParentUpgrade.targetVersion} is not an upgrade from installed ${finding.recommendedParentUpgrade.currentVersion}.`, - }); + if ( + finding.relationship === "transitive" && + finding.recommendedParentUpgrade + ) { + skippedByKey.set( + `${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, + { + package: finding.pkg.name, + version: finding.pkg.version, + relationship: finding.relationship, + reason: `Suggested parent target ${finding.recommendedParentUpgrade.package}@${finding.recommendedParentUpgrade.targetVersion} is not an upgrade from installed ${finding.recommendedParentUpgrade.currentVersion}.`, + }, + ); continue; } if ( packageManager !== "npm" && finding.relationship === "transitive" && - finding.recommendedNpmTransitiveRemediation?.kind === "update-parent-within-range" + finding.recommendedNpmTransitiveRemediation?.kind === + "update-parent-within-range" ) { - const lockfileRefreshCommand = packageManager === "pnpm" - ? buildPnpmLockfileRefreshCommand(finding.pkg.name, finding.recommendedNpmTransitiveRemediation.workspaces) - : packageManager === "yarn" - ? `yarn upgrade ${finding.pkg.name}` - : `bun update ${finding.pkg.name}`; + const lockfileRefreshCommand = + packageManager === "pnpm" + ? buildPnpmLockfileRefreshCommand( + finding.pkg.name, + finding.recommendedNpmTransitiveRemediation.workspaces, + ) + : packageManager === "yarn" + ? `yarn upgrade ${finding.pkg.name}` + : `bun update ${finding.pkg.name}`; upsertTarget(targetsByPackage, { package: finding.pkg.name, currentVersion: finding.pkg.version, - targetVersion: finding.recommendedNpmTransitiveRemediation.targetChildVersion, + targetVersion: + finding.recommendedNpmTransitiveRemediation.targetChildVersion, displayTargetVersion: "lockfile refresh", scannedVersions: null, knownVulnerableVersions: null, @@ -372,8 +452,13 @@ export function buildSuggestedFixCommandPlan( const primaryParent = getPrimaryParent(finding); if (finding.relationship === "transitive" && primaryParent) { if (finding.chainResolution) { - const isDev = devLookup.get(`${finding.chainResolution.directDep}@${finding.chainResolution.directDepCurrentVersion}`) ?? false; - const coverage = calculatePathCoverage(finding.dependencyPaths, [finding.chainResolution.directDep]); + const isDev = + devLookup.get( + `${finding.chainResolution.directDep}@${finding.chainResolution.directDepCurrentVersion}`, + ) ?? false; + const coverage = calculatePathCoverage(finding.dependencyPaths, [ + finding.chainResolution.directDep, + ]); upsertTarget(targetsByPackage, { package: finding.chainResolution.directDep, currentVersion: finding.chainResolution.directDepCurrentVersion, @@ -393,27 +478,35 @@ export function buildSuggestedFixCommandPlan( }); continue; } - const fixHint = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + const fixHint = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; const fixClause = fixHint ? ` - check for a release that resolves ${finding.pkg.name} to ${fixHint}+` : ""; - skippedByKey.set(`${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, { - package: finding.pkg.name, - version: finding.pkg.version, - relationship: finding.relationship, - reason: `${finding.pkg.name}@${finding.pkg.version} is pulled in by ${primaryParent}. No safe upgrade version for ${primaryParent} was identified automatically${fixClause}.`, - }); + skippedByKey.set( + `${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, + { + package: finding.pkg.name, + version: finding.pkg.version, + relationship: finding.relationship, + reason: `${finding.pkg.name}@${finding.pkg.version} is pulled in by ${primaryParent}. No safe upgrade version for ${primaryParent} was identified automatically${fixClause}.`, + }, + ); continue; } - skippedByKey.set(`${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, { - package: finding.pkg.name, - version: finding.pkg.version, - relationship: finding.relationship, - reason: finding.relationship === "transitive" - ? `No dependency path available for ${finding.pkg.name}@${finding.pkg.version}. Inspect your lockfile to find which package pulls it in.` - : "No confident automatic fix command could be generated for this issue.", - }); + skippedByKey.set( + `${finding.relationship}:${finding.pkg.name}@${finding.pkg.version}`, + { + package: finding.pkg.name, + version: finding.pkg.version, + relationship: finding.relationship, + reason: + finding.relationship === "transitive" + ? `No dependency path available for ${finding.pkg.name}@${finding.pkg.version}. Inspect your lockfile to find which package pulls it in.` + : "No confident automatic fix command could be generated for this issue.", + }, + ); } const targets = [...targetsByPackage.values()].sort((a, b) => { @@ -423,16 +516,18 @@ export function buildSuggestedFixCommandPlan( const urgentDelta = Number(b.urgent) - Number(a.urgent); if (urgentDelta !== 0) return urgentDelta; - const kindScore = (target: SuggestedFixTarget) => target.kind === "direct" ? 1 : 0; + const kindScore = (target: SuggestedFixTarget) => + target.kind === "direct" ? 1 : 0; const kindDelta = kindScore(b) - kindScore(a); if (kindDelta !== 0) return kindDelta; return a.package.localeCompare(b.package); }); - const skipped = [...skippedByKey.values()].sort((a, b) => a.package.localeCompare(b.package)); + const skipped = [...skippedByKey.values()].sort((a, b) => + a.package.localeCompare(b.package), + ); const sections = buildSections(targets, packageManager); - const command = targets.length > 0 - ? buildCommandForTargets(targets, packageManager) - : null; + const command = + targets.length > 0 ? buildCommandForTargets(targets, packageManager) : null; const plan: SuggestedFixCommandPlan = { packageManager, @@ -445,7 +540,9 @@ export function buildSuggestedFixCommandPlan( totalFindingCount: findings.length, }; - plan.coveredFindingCount = findings.filter(f => findSuggestedCommandForFinding(plan, f) !== null).length; + plan.coveredFindingCount = findings.filter( + (f) => findSuggestedCommandForFinding(plan, f) !== null, + ).length; if (options?.subfolder) { const prefix = `cd ${options.subfolder} && `; @@ -458,7 +555,9 @@ export function buildSuggestedFixCommandPlan( return plan; } -function inferPackageManager(scanInput: ScanInput): SuggestedFixPackageManager | null { +function inferPackageManager( + scanInput: ScanInput, +): SuggestedFixPackageManager | null { if (scanInput.source === "package-lock") return "npm"; if (scanInput.source === "pnpm-lock") return "pnpm"; if (scanInput.source === "yarn-lock") return "yarn"; @@ -467,7 +566,7 @@ function inferPackageManager(scanInput: ScanInput): SuggestedFixPackageManager | } function buildNpmUpdateCommand(pkg: string, workspaces?: string[]): string { - const nonRoot = (workspaces ?? []).filter(w => w !== "."); + const nonRoot = (workspaces ?? []).filter((w) => w !== "."); if (nonRoot.length === 1) { return `npm update --workspace=${nonRoot[0]} ${pkg}`; } @@ -477,7 +576,10 @@ function buildNpmUpdateCommand(pkg: string, workspaces?: string[]): string { return `npm update ${pkg}`; } -function buildPnpmLockfileRefreshCommand(pkg: string, workspaces?: string[]): string { +function buildPnpmLockfileRefreshCommand( + pkg: string, + workspaces?: string[], +): string { if (!workspaces || workspaces.length === 0 || workspaces.length > 1) { return `pnpm update --recursive --no-save ${pkg}`; } @@ -488,7 +590,9 @@ function buildPnpmLockfileRefreshCommand(pkg: string, workspaces?: string[]): st return `pnpm -C ${workspace} update --no-save ${pkg}`; } -export function commandPrefix(packageManager: SuggestedFixPackageManager): string { +export function commandPrefix( + packageManager: SuggestedFixPackageManager, +): string { if (packageManager === "npm") return "npm install"; if (packageManager === "pnpm") return "pnpm add"; if (packageManager === "bun") return "bun add"; @@ -503,23 +607,33 @@ export function findSuggestedCommandForFinding( plan: SuggestedFixCommandPlan, finding: Finding, ): string | null { - const target = plan.targets.find(item => { - if (finding.recommendedNpmTransitiveRemediation?.kind === "update-parent-within-range") { + const target = plan.targets.find((item) => { + if ( + finding.recommendedNpmTransitiveRemediation?.kind === + "update-parent-within-range" + ) { return ( item.kind === "parent-update" && - item.targetVersion === finding.recommendedNpmTransitiveRemediation.targetChildVersion && - (item.package === finding.recommendedNpmTransitiveRemediation.package || item.package === finding.pkg.name) + item.targetVersion === + finding.recommendedNpmTransitiveRemediation.targetChildVersion && + (item.package === finding.recommendedNpmTransitiveRemediation.package || + item.package === finding.pkg.name) ); } if (finding.recommendedParentUpgrade) { - if (item.package !== finding.recommendedParentUpgrade.package) return false; - if (item.targetVersion === finding.recommendedParentUpgrade.targetVersion) return true; + if (item.package !== finding.recommendedParentUpgrade.package) + return false; + if (item.targetVersion === finding.recommendedParentUpgrade.targetVersion) + return true; // A command targeting a higher version also satisfies a lower-version recommendation return ( looksLikeVersion(item.targetVersion) && looksLikeVersion(finding.recommendedParentUpgrade.targetVersion) && - compareVersions(item.targetVersion, finding.recommendedParentUpgrade.targetVersion) >= 0 + compareVersions( + item.targetVersion, + finding.recommendedParentUpgrade.targetVersion, + ) >= 0 ); } @@ -530,14 +644,20 @@ export function findSuggestedCommandForFinding( ); } - const directTarget = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; - return item.package === finding.pkg.name && item.targetVersion === directTarget; + const directTarget = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + return ( + item.package === finding.pkg.name && item.targetVersion === directTarget + ); }); if (!target) return null; if (target.command) return target.command; if (target.workspaces?.length) { - const commands = buildWorkspaceInstallCommands([target], plan.packageManager); + const commands = buildWorkspaceInstallCommands( + [target], + plan.packageManager, + ); if (commands.length > 0) return commands.join(" && "); } const flag = target.isDev ? ` ${devFlag(plan.packageManager)}` : ""; @@ -569,30 +689,46 @@ function upsertTarget( const merged: SuggestedFixTarget = { ...existing, urgent: existing.urgent || next.urgent, - severity: severityOrder[next.severity] > severityOrder[existing.severity] ? next.severity : existing.severity, + severity: + severityOrder[next.severity] > severityOrder[existing.severity] + ? next.severity + : existing.severity, adjusted: existing.adjusted || next.adjusted, adjustmentNote: existing.adjustmentNote ?? next.adjustmentNote ?? null, - kind: existing.kind === "direct" || next.kind === "direct" - ? "direct" - : existing.kind === "parent-update" || next.kind === "parent-update" - ? "parent-update" - : "parent-upgrade", - displayTargetVersion: existing.displayTargetVersion ?? next.displayTargetVersion, + kind: + existing.kind === "direct" || next.kind === "direct" + ? "direct" + : existing.kind === "parent-update" || next.kind === "parent-update" + ? "parent-update" + : "parent-upgrade", + displayTargetVersion: + existing.displayTargetVersion ?? next.displayTargetVersion, command: existing.command ?? next.command, isDev: (existing.isDev ?? false) && (next.isDev ?? false), workspaces: mergeStringArrays(existing.workspaces, next.workspaces), - coverage: existing.coverage === "partial" || next.coverage === "partial" - ? "partial" - : existing.coverage ?? next.coverage, - coveredPaths: mergePathArrays(existing.coveredPaths ?? [], next.coveredPaths ?? []), - remainingPaths: mergePathArrays(existing.remainingPaths ?? [], next.remainingPaths ?? []), + coverage: + existing.coverage === "partial" || next.coverage === "partial" + ? "partial" + : (existing.coverage ?? next.coverage), + coveredPaths: mergePathArrays( + existing.coveredPaths ?? [], + next.coveredPaths ?? [], + ), + remainingPaths: mergePathArrays( + existing.remainingPaths ?? [], + next.remainingPaths ?? [], + ), usage: existing.usage || next.usage, chainProof: existing.chainProof ?? next.chainProof, chainSafeVersion: existing.chainSafeVersion ?? next.chainSafeVersion, - chainVulnerablePackage: existing.chainVulnerablePackage ?? next.chainVulnerablePackage, + chainVulnerablePackage: + existing.chainVulnerablePackage ?? next.chainVulnerablePackage, }; - if (looksLikeVersion(existing.targetVersion) && looksLikeVersion(next.targetVersion)) { + if ( + looksLikeVersion(existing.targetVersion) && + looksLikeVersion(next.targetVersion) + ) { if (compareVersions(next.targetVersion, existing.targetVersion) > 0) { merged.targetVersion = next.targetVersion; merged.currentVersion = next.currentVersion ?? merged.currentVersion; @@ -601,8 +737,10 @@ function upsertTarget( if (severityOrder[next.severity] > severityOrder[existing.severity]) { merged.reason = next.reason; } - merged.scannedVersions = next.scannedVersions ?? merged.scannedVersions ?? null; - merged.knownVulnerableVersions = next.knownVulnerableVersions ?? merged.knownVulnerableVersions ?? null; + merged.scannedVersions = + next.scannedVersions ?? merged.scannedVersions ?? null; + merged.knownVulnerableVersions = + next.knownVulnerableVersions ?? merged.knownVulnerableVersions ?? null; } targetsByPackage.set(next.package, merged); return; @@ -613,8 +751,10 @@ function upsertTarget( merged.targetVersion = next.targetVersion; merged.reason = next.reason; merged.adjustmentNote = next.adjustmentNote ?? merged.adjustmentNote; - merged.scannedVersions = next.scannedVersions ?? merged.scannedVersions ?? null; - merged.knownVulnerableVersions = next.knownVulnerableVersions ?? merged.knownVulnerableVersions ?? null; + merged.scannedVersions = + next.scannedVersions ?? merged.scannedVersions ?? null; + merged.knownVulnerableVersions = + next.knownVulnerableVersions ?? merged.knownVulnerableVersions ?? null; } targetsByPackage.set(next.package, merged); @@ -622,15 +762,22 @@ function upsertTarget( function buildParentUpgradeReason( finding: Finding, - coverage: { coverage: "complete" | "partial"; coveredPaths: string[][]; remainingPaths: string[][] }, + coverage: { + coverage: "complete" | "partial"; + coveredPaths: string[][]; + remainingPaths: string[][]; + }, ): string { const coveredPath = coverage.coveredPaths[0]; - const pathText = coveredPath ? ` for ${formatDependencyPath(coveredPath)}` : ""; - const base = coverage.coverage === "complete" - ? `Parent upgrade for vulnerable ${finding.pkg.name}@${finding.pkg.version}${pathText}` - : coveredPath - ? `Path-specific parent upgrade for ${formatDependencyPath(coveredPath)} (${finding.pkg.name}@${finding.pkg.version})` - : `Path-specific parent upgrade for vulnerable ${finding.pkg.name}@${finding.pkg.version}`; + const pathText = coveredPath + ? ` for ${formatDependencyPath(coveredPath)}` + : ""; + const base = + coverage.coverage === "complete" + ? `Parent upgrade for vulnerable ${finding.pkg.name}@${finding.pkg.version}${pathText}` + : coveredPath + ? `Path-specific parent upgrade for ${formatDependencyPath(coveredPath)} (${finding.pkg.name}@${finding.pkg.version})` + : `Path-specific parent upgrade for vulnerable ${finding.pkg.name}@${finding.pkg.version}`; if (coverage.coverage === "complete") return base; @@ -658,8 +805,12 @@ function mergePathArrays(left: string[][], right: string[][]): string[][] { return output; } -function isUpgradeTarget(currentVersion: string, targetVersion: string): boolean { - if (!currentVersion || !targetVersion || currentVersion === targetVersion) return false; +function isUpgradeTarget( + currentVersion: string, + targetVersion: string, +): boolean { + if (!currentVersion || !targetVersion || currentVersion === targetVersion) + return false; if (looksLikeVersion(currentVersion) && looksLikeVersion(targetVersion)) { return compareVersions(targetVersion, currentVersion) > 0; @@ -673,7 +824,16 @@ function buildSections( packageManager: SuggestedFixPackageManager, ): SuggestedFixCommandPlan["sections"] { const groups: SuggestedFixCommandPlan["sections"] = []; - const sectionOrder: Array<{ kind: "urgent" | "direct" | "direct-adjusted" | "parent-upgrade" | "parent-update" | "validated-chain-upgrade"; severity: SeverityLabel }> = [ + const sectionOrder: Array<{ + kind: + | "urgent" + | "direct" + | "direct-adjusted" + | "parent-upgrade" + | "parent-update" + | "validated-chain-upgrade"; + severity: SeverityLabel; + }> = [ { kind: "urgent", severity: "critical" }, { kind: "parent-update", severity: "critical" }, { kind: "urgent", severity: "high" }, @@ -698,13 +858,17 @@ function buildSections( ]; for (const entry of sectionOrder) { - const sectionTargets = targets.filter(target => { - const targetKind = target.kind === "parent-update" - ? "parent-update" - : target.urgent ? "urgent" - : target.adjusted ? "direct-adjusted" - : target.chainProof !== undefined ? "validated-chain-upgrade" - : target.kind; + const sectionTargets = targets.filter((target) => { + const targetKind = + target.kind === "parent-update" + ? "parent-update" + : target.urgent + ? "urgent" + : target.adjusted + ? "direct-adjusted" + : target.chainProof !== undefined + ? "validated-chain-upgrade" + : target.kind; return targetKind === entry.kind && target.severity === entry.severity; }); @@ -724,12 +888,19 @@ function buildSections( } function buildSectionTitle( - kind: "urgent" | "direct" | "direct-adjusted" | "parent-upgrade" | "parent-update" | "validated-chain-upgrade", + kind: + | "urgent" + | "direct" + | "direct-adjusted" + | "parent-upgrade" + | "parent-update" + | "validated-chain-upgrade", severity: SeverityLabel, ): string { - const severityTitle = severity === "unknown" - ? "Unknown severity" - : `${capitalize(severity)} severity`; + const severityTitle = + severity === "unknown" + ? "Unknown severity" + : `${capitalize(severity)} severity`; if (kind === "urgent") { return severity === "critical" @@ -756,7 +927,9 @@ function buildSectionTitle( return `${severityTitle} parent upgrades`; } -function groupByWorkspaceKey(targets: SuggestedFixTarget[]): Map { +function groupByWorkspaceKey( + targets: SuggestedFixTarget[], +): Map { const groups = new Map(); for (const target of targets) { const key = (target.workspaces ?? []).slice().sort().join("\0"); @@ -776,20 +949,29 @@ function buildWorkspaceInstallCommands( for (const [wsKey, groupTargets] of groups) { const workspaces = wsKey ? wsKey.split("\0") : []; - const devTargets = groupTargets.filter(t => t.isDev); - const prodTargets = groupTargets.filter(t => !t.isDev); + const devTargets = groupTargets.filter((t) => t.isDev); + const prodTargets = groupTargets.filter((t) => !t.isDev); - for (const [targets, isDev] of [[prodTargets, false], [devTargets, true]] as const) { + for (const [targets, isDev] of [ + [prodTargets, false], + [devTargets, true], + ] as const) { if (targets.length === 0) continue; - const pkgArgs = targets.map(t => `${t.package}@${t.targetVersion}`).join(" "); + const pkgArgs = targets + .map((t) => `${t.package}@${t.targetVersion}`) + .join(" "); const flag = isDev ? ` ${devFlag(packageManager)}` : ""; if (packageManager === "npm") { - const wsFlags = workspaces.map(ws => `-w ${ws}`).join(" "); - commands.push(`npm install${flag}${wsFlags ? " " + wsFlags : ""} ${pkgArgs}`); + const wsFlags = workspaces.map((ws) => `-w ${ws}`).join(" "); + commands.push( + `npm install${flag}${wsFlags ? " " + wsFlags : ""} ${pkgArgs}`, + ); } else if (packageManager === "pnpm") { - const wsFlags = workspaces.map(ws => `--filter ./${ws}`).join(" "); - commands.push(`pnpm add${flag}${wsFlags ? " " + wsFlags : ""} ${pkgArgs}`); + const wsFlags = workspaces.map((ws) => `--filter ./${ws}`).join(" "); + commands.push( + `pnpm add${flag}${wsFlags ? " " + wsFlags : ""} ${pkgArgs}`, + ); } else if (packageManager === "yarn") { if (workspaces.length === 0) { commands.push(`yarn add${flag} ${pkgArgs}`); @@ -799,8 +981,10 @@ function buildWorkspaceInstallCommands( } } } else if (packageManager === "bun") { - const wsFlags = workspaces.map(ws => `--filter ${ws}`).join(" "); - commands.push(`bun add${flag}${wsFlags ? " " + wsFlags : ""} ${pkgArgs}`); + const wsFlags = workspaces.map((ws) => `--filter ${ws}`).join(" "); + commands.push( + `bun add${flag}${wsFlags ? " " + wsFlags : ""} ${pkgArgs}`, + ); } else { commands.push(`${commandPrefix(packageManager)}${flag} ${pkgArgs}`); } @@ -815,34 +999,47 @@ function buildCommandForTargets( packageManager: SuggestedFixPackageManager, ): string { const explicitCommands = targets - .map(target => target.command) + .map((target) => target.command) .filter((value): value is string => Boolean(value)); const commandParts: string[] = []; - const parentUpdateTargets = targets.filter(target => target.kind === "parent-update"); - const installTargets = targets.filter(target => target.kind !== "parent-update"); + const parentUpdateTargets = targets.filter( + (target) => target.kind === "parent-update", + ); + const installTargets = targets.filter( + (target) => target.kind !== "parent-update", + ); if (parentUpdateTargets.length > 0) { - const updateCommands = explicitCommands.length > 0 - ? [...new Set(explicitCommands)] - : packageManager === "npm" - ? [`npm update ${parentUpdateTargets.map(target => target.package).join(" ")}`] - : []; + const updateCommands = + explicitCommands.length > 0 + ? [...new Set(explicitCommands)] + : packageManager === "npm" + ? [ + `npm update ${parentUpdateTargets.map((target) => target.package).join(" ")}`, + ] + : []; commandParts.push(...updateCommands); } if (installTargets.length > 0) { - const hasWorkspaces = installTargets.some(t => t.workspaces?.length); + const hasWorkspaces = installTargets.some((t) => t.workspaces?.length); if (hasWorkspaces) { - commandParts.push(...buildWorkspaceInstallCommands(installTargets, packageManager)); + commandParts.push( + ...buildWorkspaceInstallCommands(installTargets, packageManager), + ); } else { - const prodInstall = installTargets.filter(t => !t.isDev); - const devInstall = installTargets.filter(t => t.isDev); + const prodInstall = installTargets.filter((t) => !t.isDev); + const devInstall = installTargets.filter((t) => t.isDev); if (prodInstall.length > 0) { - commandParts.push(`${commandPrefix(packageManager)} ${prodInstall.map(t => `${t.package}@${t.targetVersion}`).join(" ")}`); + commandParts.push( + `${commandPrefix(packageManager)} ${prodInstall.map((t) => `${t.package}@${t.targetVersion}`).join(" ")}`, + ); } if (devInstall.length > 0) { - commandParts.push(`${commandPrefix(packageManager)} ${devFlag(packageManager)} ${devInstall.map(t => `${t.package}@${t.targetVersion}`).join(" ")}`); + commandParts.push( + `${commandPrefix(packageManager)} ${devFlag(packageManager)} ${devInstall.map((t) => `${t.package}@${t.targetVersion}`).join(" ")}`, + ); } } } diff --git a/src/remediation/npm-registry.ts b/src/remediation/npm-registry.ts index efbd1670..02ca78d5 100644 --- a/src/remediation/npm-registry.ts +++ b/src/remediation/npm-registry.ts @@ -1,12 +1,19 @@ -import { compareVersions, isPreReleaseVersion, looksLikeVersion } from "../utils/version.js"; +import { + compareVersions, + isPreReleaseVersion, + looksLikeVersion, +} from "../utils/version.js"; import type { OsvVuln } from "../types.js"; import type { DebugLogger } from "../output/debug.js"; export type Packument = { - versions?: Record; - optionalDependencies?: Record; - }>; + versions?: Record< + string, + { + dependencies?: Record; + optionalDependencies?: Record; + } + >; }; export type PublishedFixVersionResolution = { @@ -38,7 +45,9 @@ export function clearPackumentCache(): void { packumentCache.clear(); } -export async function fetchPackument(packageName: string): Promise { +export async function fetchPackument( + packageName: string, +): Promise { if (packumentCache.has(packageName)) { return packumentCache.get(packageName) ?? null; } @@ -87,9 +96,10 @@ export async function fetchPackument(packageName: string): Promise { +export async function packageVersionExists( + packageName: string, + version: string, +): Promise { const packument = await fetchPackument(packageName); if (!packument) return null; return Boolean(packument.versions && version in packument.versions); @@ -134,7 +147,10 @@ export async function resolvePublishedFixVersion( }; } - const nearestPublishedVersion = publishedVersions.find(version => compareVersions(version, fixedVersionHint) >= 0) ?? null; + const nearestPublishedVersion = + publishedVersions.find( + (version) => compareVersions(version, fixedVersionHint) >= 0, + ) ?? null; if (nearestPublishedVersion) { return { resolvedVersion: nearestPublishedVersion, @@ -197,8 +213,8 @@ export async function resolveLowestKnownNonVulnerableVersion( const candidates = Object.keys(packument.versions ?? {}) .filter(looksLikeVersion) - .filter(v => !isPreReleaseVersion(v)) - .filter(version => compareVersions(version, installedVersion) > 0) + .filter((v) => !isPreReleaseVersion(v)) + .filter((version) => compareVersions(version, installedVersion) > 0) .sort(compareVersions); if (candidates.length === 0) { @@ -222,7 +238,11 @@ export async function resolveLowestKnownNonVulnerableVersion( let unknown = false; for (const vuln of vulnerabilities) { - const impacted = isVersionAffectedByVulnerability(packageName, candidate, vuln); + const impacted = isVersionAffectedByVulnerability( + packageName, + candidate, + vuln, + ); if (impacted === true) { affected = true; break; @@ -281,7 +301,7 @@ function isVersionAffectedByVulnerability( const affectedEntries = vuln.affected ?? []; if (affectedEntries.length === 0) return null; - const matchingEntries = affectedEntries.filter(entry => { + const matchingEntries = affectedEntries.filter((entry) => { const affectedName = entry.package?.name; const affectedEcosystem = entry.package?.ecosystem?.toLowerCase(); if (affectedName && affectedName !== packageName) return false; @@ -316,7 +336,11 @@ function isVersionAffectedByVulnerability( function isVersionAffectedByEvents( version: string, - events: Array<{ introduced?: string; fixed?: string; last_affected?: string }>, + events: Array<{ + introduced?: string; + fixed?: string; + last_affected?: string; + }>, ): boolean | null { if (events.length === 0) return null; @@ -333,11 +357,14 @@ function isVersionAffectedByEvents( if (event.fixed !== undefined || event.last_affected !== undefined) { if (event.fixed && !looksLikeVersion(event.fixed)) return null; - if (event.last_affected && !looksLikeVersion(event.last_affected)) return null; + if (event.last_affected && !looksLikeVersion(event.last_affected)) + return null; sawComparableWindow = true; const lowerBoundOk = - introduced === null || introduced === "0" || compareVersions(version, introduced) >= 0; + introduced === null || + introduced === "0" || + compareVersions(version, introduced) >= 0; const upperBoundOk = event.fixed ? compareVersions(version, event.fixed) < 0 : event.last_affected @@ -355,7 +382,8 @@ function isVersionAffectedByEvents( if (introduced !== null) { if (introduced !== "0" && !looksLikeVersion(introduced)) return null; sawComparableWindow = true; - const lowerBoundOk = introduced === "0" || compareVersions(version, introduced) >= 0; + const lowerBoundOk = + introduced === "0" || compareVersions(version, introduced) >= 0; if (lowerBoundOk) return true; } diff --git a/src/remediation/npm-transitive-graph.ts b/src/remediation/npm-transitive-graph.ts index 9bc55910..99f3377a 100644 --- a/src/remediation/npm-transitive-graph.ts +++ b/src/remediation/npm-transitive-graph.ts @@ -17,7 +17,8 @@ export function createNpmTransitiveGraph(args: { for (const node of args.nodes) { nodesById.set(node.id, { ...node }); nodeIdsByPackageKey.set(buildPackageKey(node.name, node.version), [ - ...(nodeIdsByPackageKey.get(buildPackageKey(node.name, node.version)) ?? []), + ...(nodeIdsByPackageKey.get(buildPackageKey(node.name, node.version)) ?? + []), node.id, ]); } @@ -28,14 +29,17 @@ export function createNpmTransitiveGraph(args: { edge.childNodeId, ]); - const ranges = rangeByParentNodeId.get(edge.parentNodeId) ?? new Map(); + const ranges = + rangeByParentNodeId.get(edge.parentNodeId) ?? new Map(); ranges.set(edge.childName, edge.range); rangeByParentNodeId.set(edge.parentNodeId, ranges); } return { nodeIdsFor(name: string, version: string | null): readonly string[] { - return Object.freeze([...(nodeIdsByPackageKey.get(buildPackageKey(name, version)) ?? [])]); + return Object.freeze([ + ...(nodeIdsByPackageKey.get(buildPackageKey(name, version)) ?? []), + ]); }, getNode(nodeId: string): Readonly | null { const node = nodesById.get(nodeId); @@ -65,7 +69,7 @@ export function findSafeVersionWithinParentRange(args: { const matchingCandidates = args.candidates .filter(looksLikeVersion) - .filter(candidate => versionSatisfiesRange(candidate, allowedRange)) + .filter((candidate) => versionSatisfiesRange(candidate, allowedRange)) .sort(compareVersions); return matchingCandidates.at(-1) ?? null; @@ -76,8 +80,11 @@ function versionSatisfiesRange(version: string, rawRange: string): boolean { if (!range) return false; if (range === "*" || range === "latest") return true; - const orParts = range.split("||").map(part => part.trim()).filter(Boolean); - return orParts.some(part => satisfiesAndRange(version, part)); + const orParts = range + .split("||") + .map((part) => part.trim()) + .filter(Boolean); + return orParts.some((part) => satisfiesAndRange(version, part)); } function satisfiesAndRange(version: string, range: string): boolean { @@ -85,7 +92,7 @@ function satisfiesAndRange(version: string, range: string): boolean { if (!normalized) return false; const tokens = normalized.split(/\s+/).filter(Boolean); - return tokens.every(token => satisfiesComparator(version, token)); + return tokens.every((token) => satisfiesComparator(version, token)); } function normalizeRange(range: string): string | null { @@ -119,7 +126,9 @@ function satisfiesComparator(version: string, comparator: string): boolean { const token = comparator.trim(); if (!token) return true; - const match = token.match(/^(<=|>=|<|>|=)?\s*([0-9]+\.[0-9]+\.[0-9]+(?:[-+][^\s]+)?)$/); + const match = token.match( + /^(<=|>=|<|>|=)?\s*([0-9]+\.[0-9]+\.[0-9]+(?:[-+][^\s]+)?)$/, + ); if (!match) return false; const operator = match[1] ?? "="; @@ -143,6 +152,8 @@ function satisfiesComparator(version: string, comparator: string): boolean { } function parseCoreVersion(version: string): [number, number, number] { - const [major = "0", minor = "0", patch = "0"] = version.split(/[+-]/)[0].split("."); + const [major = "0", minor = "0", patch = "0"] = version + .split(/[+-]/)[0] + .split("."); return [Number(major), Number(minor), Number(patch)]; } diff --git a/src/remediation/npm-transitive-resolution.ts b/src/remediation/npm-transitive-resolution.ts index ff235ee2..2f6552a1 100644 --- a/src/remediation/npm-transitive-resolution.ts +++ b/src/remediation/npm-transitive-resolution.ts @@ -1,5 +1,9 @@ import type { Finding, PackageRef } from "../types.js"; -import { compareVersions, isPreReleaseVersion, looksLikeVersion } from "../utils/version.js"; +import { + compareVersions, + isPreReleaseVersion, + looksLikeVersion, +} from "../utils/version.js"; import { fetchPackument } from "./npm-registry.js"; import { findSafeVersionWithinParentRange } from "./npm-transitive-graph.js"; import type { NpmTransitiveGraph } from "../types.js"; @@ -42,12 +46,14 @@ export async function resolveTransitiveRemediationViaRegistry(args: { ); if (!directParentContext) return null; - const { directParentName, immediateParentName, directParent } = directParentContext; + const { directParentName, immediateParentName, directParent } = + directParentContext; const vulnerableName = args.finding.pkg.name; - const fixHint = args.finding.validatedFirstFixedVersion ?? args.finding.firstFixedVersion; + const fixHint = + args.finding.validatedFirstFixedVersion ?? args.finding.firstFixedVersion; if (directParentName !== immediateParentName) { - const packagesByName = new Map(args.packages.map(p => [p.name, p])); + const packagesByName = new Map(args.packages.map((p) => [p.name, p])); return resolveWithinRangeForDeepChainViaRegistry({ immediateParentName, vulnerableName, @@ -79,7 +85,7 @@ export async function resolveTransitiveRemediationViaRegistry(args: { const inRangeTarget = [...safeCandidates] .sort(compareVersions) - .filter(v => versionSatisfiesRange(v, depRange)) + .filter((v) => versionSatisfiesRange(v, depRange)) .at(-1); if (!inRangeTarget) return null; @@ -120,15 +126,17 @@ export async function resolveNpmTransitiveRemediation(args: { ); if (!directParentContext) return null; - const { directParentName, immediateParentName, directParent } = directParentContext; + const { directParentName, immediateParentName, directParent } = + directParentContext; const vulnerableName = args.finding.pkg.name; - const fixHint = args.finding.validatedFirstFixedVersion ?? args.finding.firstFixedVersion; + const fixHint = + args.finding.validatedFirstFixedVersion ?? args.finding.firstFixedVersion; // For deeper chains (directParent → ... → immediateParent → vulnerable), check if the // immediate parent's declared range for the vulnerable package already covers a safe version. // If so, a lockfile refresh of the vulnerable package itself is sufficient. if (directParentName !== immediateParentName) { - const packagesByName = new Map(args.packages.map(p => [p.name, p])); + const packagesByName = new Map(args.packages.map((p) => [p.name, p])); return resolveWithinRangeForDeepChain({ graph: args.graph, immediateParentName, @@ -190,8 +198,8 @@ export async function resolveNpmTransitiveRemediation(args: { const parentPackument = await fetchPackument(directParentName); const parentVersions = Object.keys(parentPackument?.versions ?? {}) .filter(looksLikeVersion) - .filter(version => !isPreReleaseVersion(version)) - .filter(version => compareVersions(version, directParent.version) > 0) + .filter((version) => !isPreReleaseVersion(version)) + .filter((version) => compareVersions(version, directParent.version) > 0) .sort(compareVersions); for (const version of parentVersions) { @@ -202,11 +210,20 @@ export async function resolveNpmTransitiveRemediation(args: { if (!depRange) continue; - const targetChildVersion = highestVersionSatisfyingRange(safeCandidates, depRange); + const targetChildVersion = highestVersionSatisfyingRange( + safeCandidates, + depRange, + ); if (!targetChildVersion) continue; - const stillAllowsInstalled = versionSatisfiesRange(args.finding.pkg.version, depRange); - if (stillAllowsInstalled && compareVersions(targetChildVersion, args.finding.pkg.version) <= 0) { + const stillAllowsInstalled = versionSatisfiesRange( + args.finding.pkg.version, + depRange, + ); + if ( + stillAllowsInstalled && + compareVersions(targetChildVersion, args.finding.pkg.version) <= 0 + ) { continue; } @@ -241,7 +258,7 @@ function findDirectDependency( return pkg; } const paths = pkg.paths ?? []; - if (paths.some(path => path.at(-1) === name && path.length >= 2)) { + if (paths.some((path) => path.at(-1) === name && path.length >= 2)) { return pkg; } } @@ -253,18 +270,26 @@ function resolveDirectParentContext( viaPath: string[], packages: PackageRef[], directDependencyNames?: ReadonlySet | null, -): { directParentName: string; immediateParentName: string; directParent: PackageRef } | null { +): { + directParentName: string; + immediateParentName: string; + directParent: PackageRef; +} | null { const immediateParentName = viaPath[viaPath.length - 2]; const candidateSegments = viaPath.slice(1, -1); const directParentCandidates = directDependencyNames - ? candidateSegments.filter(segment => directDependencyNames.has(segment)) + ? candidateSegments.filter((segment) => directDependencyNames.has(segment)) : [viaPath[1]].filter(Boolean); const directParentName = directParentCandidates[0]; if (!directParentName) return null; - const directParent = findDirectDependency(packages, directParentName, directDependencyNames); + const directParent = findDirectDependency( + packages, + directParentName, + directDependencyNames, + ); if (!directParent) return null; return { directParentName, immediateParentName, directParent }; @@ -290,9 +315,12 @@ async function findSafeChildCandidates( return Object.keys(packument.versions ?? {}) .filter(looksLikeVersion) - .filter(version => !isPreReleaseVersion(version)) - .filter(version => compareVersions(version, installedVersion) > 0) - .filter(version => !fixedVersionHint || compareVersions(version, fixedVersionHint) >= 0) + .filter((version) => !isPreReleaseVersion(version)) + .filter((version) => compareVersions(version, installedVersion) > 0) + .filter( + (version) => + !fixedVersionHint || compareVersions(version, fixedVersionHint) >= 0, + ) .sort(compareVersions); } @@ -303,14 +331,20 @@ function resolveParentNodeId(args: { childName: string; childVersion: string; }): string | null { - const candidateNodeIds = args.graph.nodeIdsFor(args.parentName, args.parentVersion); + const candidateNodeIds = args.graph.nodeIdsFor( + args.parentName, + args.parentVersion, + ); if (candidateNodeIds.length === 0) return null; for (const nodeId of candidateNodeIds) { const childNodeIds = args.graph.childrenFor(nodeId); const hasMatchingChild = childNodeIds.some((childNodeId) => { const childNode = args.graph.getNode(childNodeId); - return childNode?.name === args.childName && childNode.version === args.childVersion; + return ( + childNode?.name === args.childName && + childNode.version === args.childVersion + ); }); if (!hasMatchingChild) continue; @@ -327,18 +361,29 @@ function resolveParentNodeId(args: { return candidateNodeIds[0] ?? null; } -export function highestVersionSatisfyingRange(candidates: string[], rawRange: string): string | null { - const matches = candidates.filter(candidate => versionSatisfiesRange(candidate, rawRange)).sort(compareVersions); +export function highestVersionSatisfyingRange( + candidates: string[], + rawRange: string, +): string | null { + const matches = candidates + .filter((candidate) => versionSatisfiesRange(candidate, rawRange)) + .sort(compareVersions); return matches.at(-1) ?? null; } -export function versionSatisfiesRange(version: string, rawRange: string): boolean { +export function versionSatisfiesRange( + version: string, + rawRange: string, +): boolean { const range = rawRange.trim(); if (!range) return false; if (range === "*" || range === "latest") return true; - const orParts = range.split("||").map(part => part.trim()).filter(Boolean); - return orParts.some(part => satisfiesAndRange(version, part)); + const orParts = range + .split("||") + .map((part) => part.trim()) + .filter(Boolean); + return orParts.some((part) => satisfiesAndRange(version, part)); } function satisfiesAndRange(version: string, range: string): boolean { @@ -346,7 +391,7 @@ function satisfiesAndRange(version: string, range: string): boolean { if (!normalized) return false; const tokens = normalized.split(/\s+/).filter(Boolean); - return tokens.every(token => satisfiesComparator(version, token)); + return tokens.every((token) => satisfiesComparator(version, token)); } function normalizeRange(range: string): string | null { @@ -380,7 +425,9 @@ function satisfiesComparator(version: string, comparator: string): boolean { const token = comparator.trim(); if (!token) return true; - const match = token.match(/^(<=|>=|<|>|=)?\s*([0-9]+\.[0-9]+\.[0-9]+(?:[-+][^\s]+)?)$/); + const match = token.match( + /^(<=|>=|<|>|=)?\s*([0-9]+\.[0-9]+\.[0-9]+(?:[-+][^\s]+)?)$/, + ); if (!match) return false; const operator = match[1] ?? "="; @@ -404,7 +451,9 @@ function satisfiesComparator(version: string, comparator: string): boolean { } function parseCoreVersion(version: string): [number, number, number] { - const [major = "0", minor = "0", patch = "0"] = version.split(/[+-]/)[0].split("."); + const [major = "0", minor = "0", patch = "0"] = version + .split(/[+-]/)[0] + .split("."); return [Number(major), Number(minor), Number(patch)]; } @@ -419,7 +468,8 @@ async function resolveWithinRangeForDeepChainViaRegistry(args: { workspaceMap?: Map | null; }): Promise { const immediateParent = args.packagesByName.get(args.immediateParentName); - if (!immediateParent || !looksLikeVersion(immediateParent.version)) return null; + if (!immediateParent || !looksLikeVersion(immediateParent.version)) + return null; const safeCandidates = await findSafeChildCandidates( args.vulnerableName, @@ -438,7 +488,7 @@ async function resolveWithinRangeForDeepChainViaRegistry(args: { const inRangeTarget = [...safeCandidates] .sort(compareVersions) - .filter(version => versionSatisfiesRange(version, depRange)) + .filter((version) => versionSatisfiesRange(version, depRange)) .at(-1); if (!inRangeTarget) return null; @@ -471,14 +521,22 @@ async function resolveWithinRangeForDeepChain(args: { offline?: boolean; }): Promise { const immediateParent = args.packagesByName.get(args.immediateParentName); - if (!immediateParent || !looksLikeVersion(immediateParent.version)) return null; + if (!immediateParent || !looksLikeVersion(immediateParent.version)) + return null; - const nodeIds = args.graph.nodeIdsFor(args.immediateParentName, immediateParent.version); + const nodeIds = args.graph.nodeIdsFor( + args.immediateParentName, + immediateParent.version, + ); if (nodeIds.length === 0) return null; const safeCandidates = args.offline ? buildOfflineSafeCandidates(args.installedVersion, args.fixHint) - : await findSafeChildCandidates(args.vulnerableName, args.installedVersion, args.fixHint); + : await findSafeChildCandidates( + args.vulnerableName, + args.installedVersion, + args.fixHint, + ); if (safeCandidates.length === 0) return null; diff --git a/src/remediation/parent-upgrade.ts b/src/remediation/parent-upgrade.ts index 5c9e7f3e..288e2ca2 100644 --- a/src/remediation/parent-upgrade.ts +++ b/src/remediation/parent-upgrade.ts @@ -1,5 +1,13 @@ -import type { Finding, PackageRef, RecommendedParentUpgrade } from "../types.js"; -import { compareVersions, isPreReleaseVersion, looksLikeVersion } from "../utils/version.js"; +import type { + Finding, + PackageRef, + RecommendedParentUpgrade, +} from "../types.js"; +import { + compareVersions, + isPreReleaseVersion, + looksLikeVersion, +} from "../utils/version.js"; import { fetchPackument } from "./npm-registry.js"; export async function resolveRecommendedParentUpgrade( @@ -16,7 +24,7 @@ export async function resolveRecommendedParentUpgrade( const viaPath = getBestPath(finding); if (!viaPath || viaPath.length < 3) return null; - const packagesByName = new Map(packages.map(p => [p.name, p])); + const packagesByName = new Map(packages.map((p) => [p.name, p])); const directParentContext = resolveDirectParentContext( viaPath, @@ -26,7 +34,8 @@ export async function resolveRecommendedParentUpgrade( ); if (!directParentContext) return null; - const { directParentName, immediateParentName, directParent } = directParentContext; + const { directParentName, immediateParentName, directParent } = + directParentContext; const vulnerableName = finding.pkg.name; // Common reliable case: @@ -37,7 +46,8 @@ export async function resolveRecommendedParentUpgrade( directParentVersion: directParent.version, vulnerableName, vulnerableInstalledVersion: finding.pkg.version, - vulnerableFixedVersion: finding.validatedFirstFixedVersion ?? finding.firstFixedVersion, + vulnerableFixedVersion: + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion, viaPath, }); } @@ -47,16 +57,18 @@ export async function resolveRecommendedParentUpgrade( // package) — that's what the direct parent actually lists in its own dependencies. const directParentIdx = viaPath.indexOf(directParentName); if (directParentIdx < 0) return null; - const immediateChildName = viaPath[directParentIdx + 1] ?? immediateParentName; + const immediateChildName = + viaPath[directParentIdx + 1] ?? immediateParentName; return findUpgradeForImmediateIntermediate({ directParentName, directParentVersion: directParent.version, immediateChildName, - immediateChildInstalledVersion: findPackageVersion( - packages, - immediateChildName, - viaPath.slice(0, directParentIdx + 2), - ) ?? "", + immediateChildInstalledVersion: + findPackageVersion( + packages, + immediateChildName, + viaPath.slice(0, directParentIdx + 2), + ) ?? "", vulnerableName, viaPath, }); @@ -80,7 +92,7 @@ function findDirectDependency( for (const pkg of packages) { if (pkg.name !== name) continue; const paths = pkg.paths ?? []; - if (paths.some(path => path.at(-1) === name && path.length >= 2)) { + if (paths.some((path) => path.at(-1) === name && path.length >= 2)) { return pkg; } } @@ -92,18 +104,27 @@ function resolveDirectParentContext( packages: PackageRef[], directDependencyNames: ReadonlySet | null | undefined, packagesByName: Map, -): { directParentName: string; immediateParentName: string; directParent: PackageRef } | null { +): { + directParentName: string; + immediateParentName: string; + directParent: PackageRef; +} | null { const immediateParentName = viaPath[viaPath.length - 2]; const candidateSegments = viaPath.slice(1, -1); const directParentCandidates = directDependencyNames - ? candidateSegments.filter(segment => directDependencyNames.has(segment)) + ? candidateSegments.filter((segment) => directDependencyNames.has(segment)) : [viaPath[1]].filter(Boolean); const directParentName = directParentCandidates[0]; if (!directParentName) return null; - const directParent = findDirectDependency(packages, directParentName, directDependencyNames, packagesByName); + const directParent = findDirectDependency( + packages, + directParentName, + directDependencyNames, + packagesByName, + ); if (!directParent) return null; return { directParentName, immediateParentName, directParent }; @@ -121,7 +142,7 @@ function findPackageVersion( for (const pkg of packages) { if (pkg.name !== name) continue; const paths = pkg.paths ?? []; - if (paths.some(path => startsWithPath(path, pathPrefix))) { + if (paths.some((path) => startsWithPath(path, pathPrefix))) { return pkg.version; } } @@ -151,8 +172,8 @@ async function findUpgradeForExactDirectChild( const packument = await fetchPackument(args.directParentName); const versions = Object.keys(packument?.versions ?? {}) .filter(looksLikeVersion) - .filter(v => !isPreReleaseVersion(v)) - .filter(version => compareVersions(version, args.directParentVersion) > 0) + .filter((v) => !isPreReleaseVersion(v)) + .filter((version) => compareVersions(version, args.directParentVersion) > 0) .sort(compareVersions); for (const version of versions) { @@ -198,15 +219,18 @@ type ImmediateIntermediateArgs = { async function findUpgradeForImmediateIntermediate( args: ImmediateIntermediateArgs, ): Promise { - if (!args.immediateChildInstalledVersion || !looksLikeVersion(args.immediateChildInstalledVersion)) { + if ( + !args.immediateChildInstalledVersion || + !looksLikeVersion(args.immediateChildInstalledVersion) + ) { return null; } const packument = await fetchPackument(args.directParentName); const versions = Object.keys(packument?.versions ?? {}) .filter(looksLikeVersion) - .filter(v => !isPreReleaseVersion(v)) - .filter(version => compareVersions(version, args.directParentVersion) > 0) + .filter((v) => !isPreReleaseVersion(v)) + .filter((version) => compareVersions(version, args.directParentVersion) > 0) .sort(compareVersions); for (const version of versions) { @@ -243,8 +267,11 @@ function versionSatisfiesRange(version: string, rawRange: string): boolean { if (!range) return false; if (range === "*" || range === "latest") return true; - const orParts = range.split("||").map(part => part.trim()).filter(Boolean); - return orParts.some(part => satisfiesAndRange(version, part)); + const orParts = range + .split("||") + .map((part) => part.trim()) + .filter(Boolean); + return orParts.some((part) => satisfiesAndRange(version, part)); } function satisfiesAndRange(version: string, range: string): boolean { @@ -252,7 +279,7 @@ function satisfiesAndRange(version: string, range: string): boolean { if (!normalized) return false; const tokens = normalized.split(/\s+/).filter(Boolean); - return tokens.every(token => satisfiesComparator(version, token)); + return tokens.every((token) => satisfiesComparator(version, token)); } function normalizeRange(range: string): string | null { @@ -286,7 +313,9 @@ function satisfiesComparator(version: string, comparator: string): boolean { const token = comparator.trim(); if (!token) return true; - const match = token.match(/^(<=|>=|<|>|=)?\s*([0-9]+\.[0-9]+\.[0-9]+(?:[-+][^\s]+)?)$/); + const match = token.match( + /^(<=|>=|<|>|=)?\s*([0-9]+\.[0-9]+\.[0-9]+(?:[-+][^\s]+)?)$/, + ); if (!match) return false; const operator = match[1] ?? "="; @@ -312,6 +341,6 @@ function satisfiesComparator(version: string, comparator: string): boolean { function parseCoreVersion(version: string): [number, number, number] { const [major, minor, patch] = version .split(".") - .map(part => Number(part.replace(/[^0-9].*$/, ""))); + .map((part) => Number(part.replace(/[^0-9].*$/, ""))); return [major || 0, minor || 0, patch || 0]; } diff --git a/src/remediation/transitive-chain-resolver.ts b/src/remediation/transitive-chain-resolver.ts index 7d87e4e5..a9108062 100644 --- a/src/remediation/transitive-chain-resolver.ts +++ b/src/remediation/transitive-chain-resolver.ts @@ -2,8 +2,15 @@ import type { Finding, ChainHop, ChainResolution } from "../types.js"; import type { SuggestedFixPackageManager } from "./fix-commands.js"; import { commandPrefix } from "./fix-commands.js"; import { fetchPackument } from "./npm-registry.js"; -import { compareVersions, looksLikeVersion, isPreReleaseVersion } from "../utils/version.js"; -import { versionSatisfiesRange, highestVersionSatisfyingRange } from "./npm-transitive-resolution.js"; +import { + compareVersions, + looksLikeVersion, + isPreReleaseVersion, +} from "../utils/version.js"; +import { + versionSatisfiesRange, + highestVersionSatisfyingRange, +} from "./npm-transitive-resolution.js"; export async function resolveChainFix( finding: Finding, @@ -14,14 +21,17 @@ export async function resolveChainFix( if (options.offline) return null; if (finding.relationship !== "transitive") return null; - const fixedVersion = finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; + const fixedVersion = + finding.validatedFirstFixedVersion ?? finding.firstFixedVersion; if (!fixedVersion) return null; - const uniqueDirectDeps = [...new Set( - finding.dependencyPaths - .filter(path => path.length >= 3) - .map(path => path[1]), - )]; + const uniqueDirectDeps = [ + ...new Set( + finding.dependencyPaths + .filter((path) => path.length >= 3) + .map((path) => path[1]), + ), + ]; for (const directDepName of uniqueDirectDeps) { const directDepCurrentVersion = installedVersions.get(directDepName); @@ -32,14 +42,17 @@ export async function resolveChainFix( const candidateVersions = Object.keys(packument.versions) .filter(looksLikeVersion) - .filter(v => !isPreReleaseVersion(v)) - .filter(v => compareVersions(v, directDepCurrentVersion) > 0) + .filter((v) => !isPreReleaseVersion(v)) + .filter((v) => compareVersions(v, directDepCurrentVersion) > 0) .sort(compareVersions); - const pathsForDep = finding.dependencyPaths.filter(p => p[1] === directDepName); + const pathsForDep = finding.dependencyPaths.filter( + (p) => p[1] === directDepName, + ); for (const candidateVersion of candidateVersions) { - const candidateDeps = packument.versions[candidateVersion]?.dependencies ?? {}; + const candidateDeps = + packument.versions[candidateVersion]?.dependencies ?? {}; // hops = everything after the direct dep and before (and including) the vulnerable package // For path ["root", "express", "send", "mime-types"]: // directDep = path[1] = "express" @@ -95,8 +108,8 @@ async function traceChain( const candidates = Object.keys(packument.versions) .filter(looksLikeVersion) - .filter(v => !isPreReleaseVersion(v)) - .filter(v => compareVersions(v, fixedVersion) >= 0) + .filter((v) => !isPreReleaseVersion(v)) + .filter((v) => compareVersions(v, fixedVersion) >= 0) .sort(compareVersions); const safeVersion = highestVersionSatisfyingRange(candidates, range); @@ -111,14 +124,19 @@ async function traceChain( const candidates = Object.keys(packument.versions) .filter(looksLikeVersion) - .filter(v => !isPreReleaseVersion(v)) + .filter((v) => !isPreReleaseVersion(v)) .sort(compareVersions); const resolvedVersion = highestVersionSatisfyingRange(candidates, range); if (!resolvedVersion) return null; const nextDeps = packument.versions[resolvedVersion]?.dependencies ?? {}; - const result = await traceChain(nextDeps, remainingPath.slice(1), vulnerableInstalledVersion, fixedVersion); + const result = await traceChain( + nextDeps, + remainingPath.slice(1), + vulnerableInstalledVersion, + fixedVersion, + ); if (!result) return null; return { @@ -126,4 +144,3 @@ async function traceChain( safeVersion: result.safeVersion, }; } - diff --git a/src/scan/multi-folder-scan.ts b/src/scan/multi-folder-scan.ts index 06d384fc..a0aa98c2 100644 --- a/src/scan/multi-folder-scan.ts +++ b/src/scan/multi-folder-scan.ts @@ -1,6 +1,11 @@ import path from "node:path"; import process from "node:process"; -import type { Finding, ParsedOptions, PackageRef, SeverityLabel } from "../types.js"; +import type { + Finding, + ParsedOptions, + PackageRef, + SeverityLabel, +} from "../types.js"; import type { SuggestedFixCommandPlan } from "../remediation/fix-commands.js"; import type { ScanInput } from "../types.js"; import { loadMultiplePackages } from "../parsers/multi-package.js"; @@ -10,7 +15,11 @@ import { normalizeSeverity } from "../osv/severity.js"; import { selectFindingsForTable } from "../output/finding-display.js"; import { buildSuggestedFixCommandPlan } from "../remediation/fix-commands.js"; import { readDirectDependencyNames } from "../utils/package-json.js"; -import { DEFAULT_BATCH_SIZE, DEFAULT_SEARCH_DEPTH, severityOrder } from "../constants.js"; +import { + DEFAULT_BATCH_SIZE, + DEFAULT_SEARCH_DEPTH, + severityOrder, +} from "../constants.js"; import { printMultiFolderResults } from "../output/multi-folder-printer.js"; import { writeMultiFolderHtmlReport } from "../output/multi-folder-html-reporter.js"; import { chalk } from "../utils/chalk.js"; @@ -32,8 +41,15 @@ export async function runMultiFolderScan(params: { batchSize: number; options: ParsedOptions; }): Promise { - const searchDepth = Math.max(0, Number(params.options.searchDepth || DEFAULT_SEARCH_DEPTH)); - const folders = loadMultiplePackages(params.projectRoot, !!params.options.prodOnly, searchDepth); + const searchDepth = Math.max( + 0, + Number(params.options.searchDepth || DEFAULT_SEARCH_DEPTH), + ); + const folders = loadMultiplePackages( + params.projectRoot, + !!params.options.prodOnly, + searchDepth, + ); const offline = !!params.options.offline || !!params.options.offlineDb; const results: MultiFolderScanResult[] = []; @@ -45,18 +61,34 @@ export async function runMultiFolderScan(params: { } const subfolderAbs = path.join(params.projectRoot, subfolder); - const directDependencyNames = readDirectDependencyNames(subfolderAbs, !!params.options.prodOnly); - const findings = await scanPackages(scanInput.packages, params.batchSize, params.options, { - directDependencyNames, - scanSource: scanInput.source, - scanFilePath: scanInput.filePath, - }); + const directDependencyNames = readDirectDependencyNames( + subfolderAbs, + !!params.options.prodOnly, + ); + const findings = await scanPackages( + scanInput.packages, + params.batchSize, + params.options, + { + directDependencyNames, + scanSource: scanInput.source, + scanFilePath: scanInput.filePath, + }, + ); const sorted = sortFindingsForOutput(findings); const coverage = buildCoverageNotes(scanInput, offline); - const minSeverity = normalizeSeverity(params.options.minSeverity || "medium"); - const tableFindings = params.options.all ? sorted : selectFindingsForTable(sorted, minSeverity); - const suggestedFixCommands = buildSuggestedFixCommandPlan(sorted, scanInput, { offline, subfolder }); + const minSeverity = normalizeSeverity( + params.options.minSeverity || "medium", + ); + const tableFindings = params.options.all + ? sorted + : selectFindingsForTable(sorted, minSeverity); + const suggestedFixCommands = buildSuggestedFixCommandPlan( + sorted, + scanInput, + { offline, subfolder }, + ); results.push({ subfolder, @@ -87,35 +119,54 @@ export async function handleMultiFolderScan(params: { } if (params.options.fix) { - console.error(chalk.yellow("--fix is not yet supported in multi-folder mode. Run cve-lite . from each subfolder individually.")); + console.error( + chalk.yellow( + "--fix is not yet supported in multi-folder mode. Run cve-lite . from each subfolder individually.", + ), + ); process.exit(1); return; } if (params.options.sarif || params.options.cdx) { - console.error(chalk.yellow("--sarif and --cdx are not yet supported in multi-folder mode.")); + console.error( + chalk.yellow( + "--sarif and --cdx are not yet supported in multi-folder mode.", + ), + ); process.exit(1); return; } if (params.options.json) { const { serializeFinding } = await import("../output/formatters.js"); - const allFindings = results.flatMap(r => - r.sorted.map(f => ({ ...serializeFinding(f, r.suggestedFixCommands), subfolder: r.subfolder })) + const allFindings = results.flatMap((r) => + r.sorted.map((f) => ({ + ...serializeFinding(f, r.suggestedFixCommands), + subfolder: r.subfolder, + })), + ); + console.log( + JSON.stringify( + { + multiFolder: true, + folders: results.map((r) => r.subfolder), + findings: allFindings, + scannedAt: new Date().toISOString(), + }, + null, + 2, + ), ); - console.log(JSON.stringify({ - multiFolder: true, - folders: results.map(r => r.subfolder), - findings: allFindings, - scannedAt: new Date().toISOString(), - }, null, 2)); } else { printMultiFolderResults(results, params.options); } if (params.options.report) { const outputDir = path.resolve( - typeof params.options.report === "string" ? params.options.report : "./cve-report" + typeof params.options.report === "string" + ? params.options.report + : "./cve-report", ); const cliVersion = getCliVersion(); const { reportPath } = await writeMultiFolderHtmlReport({ @@ -129,7 +180,9 @@ export async function handleMultiFolderScan(params: { } const failLevel = normalizeSeverity(params.options.failOn); - const allSorted = results.flatMap(r => r.sorted); - const shouldFail = allSorted.some(f => severityOrder[f.severity] >= severityOrder[failLevel]); + const allSorted = results.flatMap((r) => r.sorted); + const shouldFail = allSorted.some( + (f) => severityOrder[f.severity] >= severityOrder[failLevel], + ); process.exit(shouldFail ? 1 : 0); } diff --git a/src/scanner.ts b/src/scanner.ts index 93f45087..4c25d2f1 100644 --- a/src/scanner.ts +++ b/src/scanner.ts @@ -1,15 +1,36 @@ -import type { Finding, NpmTransitiveGraph, OsvVuln, PackageRef, ParsedOptions, ScanInput } from "./types.js"; +import type { + Finding, + NpmTransitiveGraph, + OsvVuln, + PackageRef, + ParsedOptions, + ScanInput, +} from "./types.js"; import { chunk, unique, runWithConcurrency } from "./utils/array.js"; -import { isPrivateRegistrySource, isGitSource, hasCommitShaPinning } from "./utils/advisory.js"; -import { compareVersions, isPreReleaseVersion, looksLikeVersion } from "./utils/version.js"; +import { + isPrivateRegistrySource, + isGitSource, + hasCommitShaPinning, +} from "./utils/advisory.js"; +import { + compareVersions, + isPreReleaseVersion, + looksLikeVersion, +} from "./utils/version.js"; import { loadCache, saveCache, isEntryStale } from "./osv/cache.js"; import { maxSeverity } from "./osv/severity.js"; import { createSpinner } from "./output/spinner.js"; import { OsvAdvisorySource } from "./advisory/osv-advisory-source.js"; import { AdvisorySource } from "./advisory/advisory-source.js"; import { LocalAdvisorySource } from "./advisory/local-advisory-source.js"; -import { AdvisoryDbMetadata, LocalAdvisoryDatabase } from "./advisory/local-db.js"; -import { ADVISORY_DB_STALE_AFTER_MS, getDefaultAdvisoryDbPath } from "./advisory/osv-sync.js"; +import { + AdvisoryDbMetadata, + LocalAdvisoryDatabase, +} from "./advisory/local-db.js"; +import { + ADVISORY_DB_STALE_AFTER_MS, + getDefaultAdvisoryDbPath, +} from "./advisory/osv-sync.js"; import { resolveRecommendedParentUpgrade } from "./remediation/parent-upgrade.js"; import { configureNpmRegistryDebug, @@ -18,7 +39,10 @@ import { resolvePublishedFixVersion, } from "./remediation/npm-registry.js"; import { countBySeverity } from "./utils/severity.js"; -import { resolveNpmTransitiveRemediation, resolveTransitiveRemediationViaRegistry } from "./remediation/npm-transitive-resolution.js"; +import { + resolveNpmTransitiveRemediation, + resolveTransitiveRemediationViaRegistry, +} from "./remediation/npm-transitive-resolution.js"; import { resolveChainFix } from "./remediation/transitive-chain-resolver.js"; import type { SuggestedFixPackageManager } from "./remediation/fix-commands.js"; import { loadNpmLockGraph } from "./parsers/npm-lock-graph.js"; @@ -28,7 +52,6 @@ import { buildBunWorkspaceMap } from "./parsers/bun-lock.js"; import { pluralize } from "./utils/string.js"; import { type DebugLogger } from "./output/debug.js"; - type ScanClassificationContext = { directDependencyNames?: ReadonlySet | null; scanSource?: ScanInput["source"]; @@ -81,7 +104,9 @@ function getPackageCacheKey(pkg: PackageRef): string { return `${pkg.ecosystem}:${pkg.name}@${pkg.version}`; } -function inferPackageManagerForChain(source: ScanInput["source"] | undefined): SuggestedFixPackageManager | null { +function inferPackageManagerForChain( + source: ScanInput["source"] | undefined, +): SuggestedFixPackageManager | null { if (source === "package-lock" || source === "npm-shrinkwrap") return "npm"; if (source === "pnpm-lock") return "pnpm"; if (source === "yarn-lock") return "yarn"; @@ -144,21 +169,26 @@ export async function scanPackages( } const chunks = chunk(uncachedPackages, batchSize); - const reason = options.noCache ? "no-cache mode" : "stale or missing entry"; - log("Cache check", { hits, misses, reason, uncachedBatches: chunks.length }); - spinner.update(`Scanning OSV in ${chunks.length} parallel ${pluralize(chunks.length, "batch", "batches")}...`); + const reason = options.noCache + ? "no-cache mode" + : "stale or missing entry"; + log("Cache check", { + hits, + misses, + reason, + uncachedBatches: chunks.length, + }); + spinner.update( + `Scanning OSV in ${chunks.length} parallel ${pluralize(chunks.length, "batch", "batches")}...`, + ); const osvScanStartedAt = Date.now(); let batchCounter = 0; - const allAdvisoryResults = await runWithConcurrency( - chunks, - 5, - c => { - batchCounter += 1; - const batchId = `b-${String(batchCounter).padStart(2, "0")}`; - return advisorySource.queryBatch(c, { batchId }); - }, - ); + const allAdvisoryResults = await runWithConcurrency(chunks, 5, (c) => { + batchCounter += 1; + const batchId = `b-${String(batchCounter).padStart(2, "0")}`; + return advisorySource.queryBatch(c, { batchId }); + }); for (let i = 0; i < chunks.length; i++) { const chunkItems = chunks[i]!; @@ -167,8 +197,13 @@ export async function scanPackages( for (let j = 0; j < chunkItems.length; j++) { const pkg = chunkItems[j]!; const row = rows[j]; - const vulnIds = (row?.vulnerabilities ?? []).map(v => v.id).filter(Boolean); - cache.queryEntries[getPackageCacheKey(pkg)] = { vulnIds, cachedAt: new Date().toISOString() }; + const vulnIds = (row?.vulnerabilities ?? []) + .map((v) => v.id) + .filter(Boolean); + cache.queryEntries[getPackageCacheKey(pkg)] = { + vulnIds, + cachedAt: new Date().toISOString(), + }; if (vulnIds.length > 0) { results.push({ pkg, vulnIds }); } @@ -186,28 +221,39 @@ export async function scanPackages( if (chunks.length === 0) { spinner.succeed("Loaded package matches from cache"); } else { - spinner.succeed(`Queried OSV in ${chunks.length} ${pluralize(chunks.length, "batch", "batches")}`); + spinner.succeed( + `Queried OSV in ${chunks.length} ${pluralize(chunks.length, "batch", "batches")}`, + ); } } else { - const advisoryResult = await advisorySource.queryBatch(packages, { batchId: "offline" }); + const advisoryResult = await advisorySource.queryBatch(packages, { + batchId: "offline", + }); const rows = advisoryResult ?? []; for (let i = 0; i < packages.length; i++) { const pkg = packages[i]; const row = rows[i]; - const vulnIds = (row?.vulnerabilities ?? []).map(v => v.id).filter(Boolean); + const vulnIds = (row?.vulnerabilities ?? []) + .map((v) => v.id) + .filter(Boolean); if (vulnIds.length > 0) { results.push({ pkg, vulnIds }); } } - spinner.succeed("Loaded package matches from the local advisory database"); + spinner.succeed( + "Loaded package matches from the local advisory database", + ); } - const idSet = new Set(results.flatMap(r => r.vulnIds)); + const idSet = new Set(results.flatMap((r) => r.vulnIds)); const vulnMap = new Map(); if (idSet.size > 0 && !offline) { const ids = [...idSet]; - const detailSpinner = createSpinner("Fetching vulnerability details...", options); + const detailSpinner = createSpinner( + "Fetching vulnerability details...", + options, + ); try { // cache.entries holds OsvVuln detail records. Unlike cache.queryEntries (which has a // 30-minute staleness check applied earlier in this function), detail records have no @@ -227,7 +273,9 @@ export async function scanPackages( // Fetch all uncached IDs concurrently if (uncachedIds.length > 0) { - detailSpinner.update(`Fetching ${uncachedIds.length} vulnerability details...`); + detailSpinner.update( + `Fetching ${uncachedIds.length} vulnerability details...`, + ); await runWithConcurrency(uncachedIds, 10, async (id) => { try { const detail = await advisorySource.getVuln(id); @@ -238,14 +286,19 @@ export async function scanPackages( } }); } - detailSpinner.succeed(`Loaded ${ids.length} vulnerability detail ${pluralize(ids.length, "record")}`); + detailSpinner.succeed( + `Loaded ${ids.length} vulnerability detail ${pluralize(ids.length, "record")}`, + ); } catch (error) { detailSpinner.fail("Failed while fetching vulnerability details"); throw error; } } else if (idSet.size > 0 && offline) { const ids = [...idSet]; - const detailSpinner = createSpinner("Loading local advisory details...", options); + const detailSpinner = createSpinner( + "Loading local advisory details...", + options, + ); try { await runWithConcurrency(ids, 10, async (id) => { try { @@ -255,7 +308,9 @@ export async function scanPackages( // ignore missing local records so scans remain resilient to partial DB state } }); - detailSpinner.succeed(`Loaded ${ids.length} local advisory detail ${pluralize(ids.length, "record")}`); + detailSpinner.succeed( + `Loaded ${ids.length} local advisory detail ${pluralize(ids.length, "record")}`, + ); } catch (error) { detailSpinner.fail("Failed while loading local advisory details"); throw error; @@ -266,14 +321,16 @@ export async function scanPackages( saveCache(cache, cacheDirOverride, options.debug ? log : undefined); } - const findings: Finding[] = results.map(result => { + const findings: Finding[] = results.map((result) => { const vulnerabilities = result.vulnIds - .map(id => vulnMap.get(id)) + .map((id) => vulnMap.get(id)) .filter((v): v is OsvVuln => Boolean(v)); const severity = maxSeverity(vulnerabilities); const cveAliases = unique( - vulnerabilities.flatMap(v => (v.aliases ?? []).filter(a => a.startsWith("CVE-"))), + vulnerabilities.flatMap((v) => + (v.aliases ?? []).filter((a) => a.startsWith("CVE-")), + ), ); const dependencyPaths = result.pkg.paths ?? []; const relationship = classifyRelationship( @@ -301,7 +358,7 @@ export async function scanPackages( }); for (const finding of findings) { - if (finding.vulnerabilities.some(v => v.id.startsWith("MAL-"))) { + if (finding.vulnerabilities.some((v) => v.id.startsWith("MAL-"))) { if (isGitSource(finding.pkg)) { finding.maliciousGitSource = true; finding.maliciousGitSourcePinned = hasCommitShaPinning(finding.pkg); @@ -311,13 +368,19 @@ export async function scanPackages( } } - const npmTransitiveGraph = context?.scanSource === "package-lock" && context.scanFilePath - ? createNpmTransitiveGraphFromLockfile(context.scanFilePath, log, packages.length) - : null; + const npmTransitiveGraph = + context?.scanSource === "package-lock" && context.scanFilePath + ? createNpmTransitiveGraphFromLockfile( + context.scanFilePath, + log, + packages.length, + ) + : null; const npmWorkspaceMap = (() => { try { return context?.scanSource === "package-lock" && context.scanFilePath - ? buildNpmWorkspaceMap(context.scanFilePath) : null; + ? buildNpmWorkspaceMap(context.scanFilePath) + : null; } catch (error) { log("Workspace map", { type: "npm", @@ -333,7 +396,8 @@ export async function scanPackages( const pnpmWorkspaceMap = (() => { try { return context?.scanSource === "pnpm-lock" && context.scanFilePath - ? buildPnpmWorkspaceMap(context.scanFilePath) : null; + ? buildPnpmWorkspaceMap(context.scanFilePath) + : null; } catch (error) { log("Workspace map", { type: "pnpm", @@ -349,7 +413,8 @@ export async function scanPackages( const bunWorkspaceMap = (() => { try { return context?.scanSource === "bun-lock" && context.scanFilePath - ? buildBunWorkspaceMap(context.scanFilePath) : null; + ? buildBunWorkspaceMap(context.scanFilePath) + : null; } catch (error) { log("Workspace map", { type: "bun", @@ -363,14 +428,26 @@ export async function scanPackages( log("Workspace map", { type: "bun", workspaces: bunWorkspaceMap.size }); } const lockfileWorkspaceMap = pnpmWorkspaceMap ?? bunWorkspaceMap ?? null; - const npmRemediationCache = new Map(); - const parentUpgradeCache = new Map(); + const npmRemediationCache = new Map< + string, + Finding["recommendedNpmTransitiveRemediation"] + >(); + const parentUpgradeCache = new Map< + string, + Finding["recommendedParentUpgrade"] + >(); const directValidationCount = offline ? 0 : findings.length; - const transitiveRemediationCount = findings.filter(finding => finding.relationship === "transitive").length; - const analysisStepCount = directValidationCount + transitiveRemediationCount; + const transitiveRemediationCount = findings.filter( + (finding) => finding.relationship === "transitive", + ).length; + const analysisStepCount = + directValidationCount + transitiveRemediationCount; if (analysisStepCount > 0) { - const analysisSpinner = createSpinner("Analyzing vulnerability findings...", options); + const analysisSpinner = createSpinner( + "Analyzing vulnerability findings...", + options, + ); let completedSteps = 0; const updateAnalysisProgress = (phase: string, subject: string) => { @@ -382,9 +459,16 @@ export async function scanPackages( try { if (!offline) { - await validateDirectFixTargets(findings, (finding) => { - updateAnalysisProgress("validating fix target for", `${finding.pkg.name}@${finding.pkg.version}`); - }, log); + await validateDirectFixTargets( + findings, + (finding) => { + updateAnalysisProgress( + "validating fix target for", + `${finding.pkg.name}@${finding.pkg.version}`, + ); + }, + log, + ); } // Pre-warm packument cache for all packages needed in the remediation loop. @@ -401,23 +485,35 @@ export async function scanPackages( } } if (packumentsToPrewarm.size > 0) { - await runWithConcurrency([...packumentsToPrewarm], 8, async (name) => { - try { await fetchPackument(name); } catch { /* loop will handle missing packuments */ } - }); + await runWithConcurrency( + [...packumentsToPrewarm], + 8, + async (name) => { + try { + await fetchPackument(name); + } catch { + /* loop will handle missing packuments */ + } + }, + ); } } for (const finding of findings) { if (finding.relationship !== "transitive") continue; - updateAnalysisProgress("resolving remediation for", `${finding.pkg.name}@${finding.pkg.version}`); + updateAnalysisProgress( + "resolving remediation for", + `${finding.pkg.name}@${finding.pkg.version}`, + ); const remediationCacheKey = JSON.stringify({ package: finding.pkg.name, version: finding.pkg.version, dependencyPaths: finding.dependencyPaths, firstFixedVersion: finding.firstFixedVersion, - validatedFirstFixedVersion: finding.validatedFirstFixedVersion ?? null, + validatedFirstFixedVersion: + finding.validatedFirstFixedVersion ?? null, }); try { @@ -426,25 +522,32 @@ export async function scanPackages( finding.recommendedNpmTransitiveRemediation = npmRemediationCache.get(remediationCacheKey) ?? undefined; } else { - finding.recommendedNpmTransitiveRemediation = await resolveNpmTransitiveRemediation({ - finding, - graph: npmTransitiveGraph, - packages, - directDependencyNames: context?.directDependencyNames, - offline, - workspaceMap: npmWorkspaceMap, - }); + finding.recommendedNpmTransitiveRemediation = + await resolveNpmTransitiveRemediation({ + finding, + graph: npmTransitiveGraph, + packages, + directDependencyNames: context?.directDependencyNames, + offline, + workspaceMap: npmWorkspaceMap, + }); npmRemediationCache.set( remediationCacheKey, finding.recommendedNpmTransitiveRemediation ?? null, ); } - if (finding.recommendedNpmTransitiveRemediation?.kind === "upgrade-parent-to-version") { + if ( + finding.recommendedNpmTransitiveRemediation?.kind === + "upgrade-parent-to-version" + ) { finding.recommendedParentUpgrade = { package: finding.recommendedNpmTransitiveRemediation.package, - currentVersion: finding.recommendedNpmTransitiveRemediation.currentVersion, - targetVersion: finding.recommendedNpmTransitiveRemediation.targetVersion ?? "", + currentVersion: + finding.recommendedNpmTransitiveRemediation.currentVersion, + targetVersion: + finding.recommendedNpmTransitiveRemediation.targetVersion ?? + "", viaPath: finding.recommendedNpmTransitiveRemediation.viaPath, vulnerablePackage: finding.pkg.name, confidence: "exact-direct-child", @@ -453,7 +556,10 @@ export async function scanPackages( continue; } - if (finding.recommendedNpmTransitiveRemediation?.kind === "update-parent-within-range") { + if ( + finding.recommendedNpmTransitiveRemediation?.kind === + "update-parent-within-range" + ) { finding.recommendedParentUpgrade = undefined; continue; } @@ -464,34 +570,43 @@ export async function scanPackages( finding.recommendedNpmTransitiveRemediation = npmRemediationCache.get(remediationCacheKey) ?? undefined; } else { - finding.recommendedNpmTransitiveRemediation = await resolveTransitiveRemediationViaRegistry({ - finding, - packages, - directDependencyNames: context?.directDependencyNames, - workspaceMap: lockfileWorkspaceMap, - }); + finding.recommendedNpmTransitiveRemediation = + await resolveTransitiveRemediationViaRegistry({ + finding, + packages, + directDependencyNames: context?.directDependencyNames, + workspaceMap: lockfileWorkspaceMap, + }); npmRemediationCache.set( remediationCacheKey, finding.recommendedNpmTransitiveRemediation ?? null, ); } - if (finding.recommendedNpmTransitiveRemediation?.kind === "update-parent-within-range") { + if ( + finding.recommendedNpmTransitiveRemediation?.kind === + "update-parent-within-range" + ) { finding.recommendedParentUpgrade = undefined; continue; } } if (parentUpgradeCache.has(remediationCacheKey)) { - finding.recommendedParentUpgrade = parentUpgradeCache.get(remediationCacheKey) ?? undefined; + finding.recommendedParentUpgrade = + parentUpgradeCache.get(remediationCacheKey) ?? undefined; } else { - finding.recommendedParentUpgrade = await resolveRecommendedParentUpgrade( - finding, - packages, - context?.directDependencyNames, - { offline }, + finding.recommendedParentUpgrade = + await resolveRecommendedParentUpgrade( + finding, + packages, + context?.directDependencyNames, + { offline }, + ); + parentUpgradeCache.set( + remediationCacheKey, + finding.recommendedParentUpgrade ?? null, ); - parentUpgradeCache.set(remediationCacheKey, finding.recommendedParentUpgrade ?? null); } } catch { finding.recommendedNpmTransitiveRemediation = undefined; @@ -504,10 +619,15 @@ export async function scanPackages( // Chain fix resolution: pre-compute validated chain for transitive findings const chainPm = inferPackageManagerForChain(context?.scanSource); if (chainPm) { - const installedVersions = new Map(packages.map(p => [p.name, p.version])); + const installedVersions = new Map( + packages.map((p) => [p.name, p.version]), + ); await Promise.all( findings - .filter(f => f.relationship === "transitive" && f.dependencyPaths?.length) + .filter( + (f) => + f.relationship === "transitive" && f.dependencyPaths?.length, + ) .map(async (finding) => { try { finding.chainResolution = await resolveChainFix( @@ -533,8 +653,11 @@ export async function scanPackages( const severityCounts = countBySeverity(findings); log("Findings classified", { total: findings.length, - direct: findings.filter(finding => finding.relationship === "direct").length, - transitive: findings.filter(finding => finding.relationship === "transitive").length, + direct: findings.filter((finding) => finding.relationship === "direct") + .length, + transitive: findings.filter( + (finding) => finding.relationship === "transitive", + ).length, critical: severityCounts.critical, high: severityCounts.high, medium: severityCounts.medium, @@ -566,7 +689,12 @@ function createNpmTransitiveGraphFromLockfile( getNode(nodeId: string) { const node = lockGraph.getNode(nodeId); return node - ? { id: node.id, name: node.name, version: node.version, packagePath: node.packagePath } + ? { + id: node.id, + name: node.name, + version: node.version, + packagePath: node.packagePath, + } : null; }, childrenFor(nodeId: string) { @@ -590,7 +718,9 @@ async function validateDirectFixTargets( onFinding?: (finding: Finding) => void, debugLog?: DebugLogger, ): Promise { - const directCandidates = findings.filter(finding => finding.vulnerabilities.length > 0); + const directCandidates = findings.filter( + (finding) => finding.vulnerabilities.length > 0, + ); for (const finding of directCandidates) { onFinding?.(finding); @@ -610,19 +740,29 @@ async function validateDirectFixTargets( const fixedVersionHint = finding.firstFixedVersion; if (lowestKnownResolution.resolvedVersion) { - finding.validatedFirstFixedVersion = lowestKnownResolution.resolvedVersion; - finding.validatedTargetScannedVersions = lowestKnownResolution.candidatesChecked; - finding.validatedTargetKnownVulnerableVersions = lowestKnownResolution.candidatesKnownVulnerable; + finding.validatedFirstFixedVersion = + lowestKnownResolution.resolvedVersion; + finding.validatedTargetScannedVersions = + lowestKnownResolution.candidatesChecked; + finding.validatedTargetKnownVulnerableVersions = + lowestKnownResolution.candidatesKnownVulnerable; - if (!fixedVersionHint || fixedVersionHint === lowestKnownResolution.resolvedVersion) { + if ( + !fixedVersionHint || + fixedVersionHint === lowestKnownResolution.resolvedVersion + ) { finding.fixVersionValidationNote = null; logFixValidation(); continue; } - const hintResolution = await resolvePublishedFixVersion(finding.pkg.name, fixedVersionHint); + const hintResolution = await resolvePublishedFixVersion( + finding.pkg.name, + fixedVersionHint, + ); if ( - hintResolution.resolvedVersion === lowestKnownResolution.resolvedVersion && + hintResolution.resolvedVersion === + lowestKnownResolution.resolvedVersion && hintResolution.note ) { finding.fixVersionValidationNote = hintResolution.note; @@ -630,8 +770,7 @@ async function validateDirectFixTargets( continue; } - finding.fixVersionValidationNote = - `Advisory fixed-version hint ${fixedVersionHint} is still known vulnerable for ${finding.pkg.name}; scanned ${lowestKnownResolution.candidatesChecked} package ${pluralize(lowestKnownResolution.candidatesChecked, "version")} above current version (${lowestKnownResolution.candidatesKnownVulnerable} still known vulnerable); using lowest known non-vulnerable version ${lowestKnownResolution.resolvedVersion}.`; + finding.fixVersionValidationNote = `Advisory fixed-version hint ${fixedVersionHint} is still known vulnerable for ${finding.pkg.name}; scanned ${lowestKnownResolution.candidatesChecked} package ${pluralize(lowestKnownResolution.candidatesChecked, "version")} above current version (${lowestKnownResolution.candidatesKnownVulnerable} still known vulnerable); using lowest known non-vulnerable version ${lowestKnownResolution.resolvedVersion}.`; logFixValidation(); continue; } @@ -645,9 +784,13 @@ async function validateDirectFixTargets( continue; } - const resolution = await resolvePublishedFixVersion(finding.pkg.name, fixedVersionHint); + const resolution = await resolvePublishedFixVersion( + finding.pkg.name, + fixedVersionHint, + ); finding.validatedFirstFixedVersion = resolution.resolvedVersion; - finding.fixVersionValidationNote = resolution.note ?? lowestKnownResolution.note; + finding.fixVersionValidationNote = + resolution.note ?? lowestKnownResolution.note; finding.validatedTargetScannedVersions = null; finding.validatedTargetKnownVulnerableVersions = null; logFixValidation(); @@ -679,12 +822,12 @@ function classifyRelationship( // one. A direct install always has at least one path of length 2 (["project", name]). // If all paths are longer, this is a different (transitive) version of the package. if (paths.length === 0) return "direct"; // no path data — trust the name - const hasDirectPath = paths.some(p => p.length <= 2); + const hasDirectPath = paths.some((p) => p.length <= 2); return hasDirectPath ? "direct" : "transitive"; } if (paths.length === 0) return "unknown"; if (directDependencyNames) return "transitive"; - const shortest = Math.min(...paths.map(p => p.length)); + const shortest = Math.min(...paths.map((p) => p.length)); if (shortest <= 2) return "direct"; return "transitive"; } @@ -707,7 +850,10 @@ function findFirstFixedVersion(vulns: OsvVuln[]): string | null { return fixedVersions.sort(compareVersions)[0]; } -export function buildCoverageNotes(scanInput: ScanInput, offline: boolean): string[] { +export function buildCoverageNotes( + scanInput: ScanInput, + offline: boolean, +): string[] { const notes = [ "CVE Lite CLI checks package versions against OSV advisories. It does not prove exploitability or runtime reachability.", "Installed node_modules contents are not verified in this scan.", @@ -716,11 +862,15 @@ export function buildCoverageNotes(scanInput: ScanInput, offline: boolean): stri ]; if (scanInput.mode === "manifest-fallback") { - notes.push("Manifest fallback is limited to direct dependencies pinned to exact versions. It does not resolve transitive dependencies from package.json alone."); + notes.push( + "Manifest fallback is limited to direct dependencies pinned to exact versions. It does not resolve transitive dependencies from package.json alone.", + ); } if (offline) { - notes.push("Offline mode uses the local advisory database and does not make outbound advisory API calls."); + notes.push( + "Offline mode uses the local advisory database and does not make outbound advisory API calls.", + ); } return notes; diff --git a/src/skills/install.ts b/src/skills/install.ts index 7205e5eb..b2e5ec85 100644 --- a/src/skills/install.ts +++ b/src/skills/install.ts @@ -18,7 +18,7 @@ function writeCursorSkill(projectRoot: string): void { fs.writeFileSync( path.join(dir, "cve-lite.mdc"), frontMatter + SKILL_CONTENT + "\n", - "utf-8" + "utf-8", ); } @@ -58,21 +58,21 @@ export function installSkill(projectRoot: string): void { console.log("\nCVE Lite CLI skills installed:\n"); console.log( - ` ${chalk.green("✓")} Claude Code .claude/commands/cve-lite.md` + ` ${chalk.green("✓")} Claude Code .claude/commands/cve-lite.md`, ); console.log( - ` ${chalk.green("✓")} Codex CLI AGENTS.md (section written)` + ` ${chalk.green("✓")} Codex CLI AGENTS.md (section written)`, ); console.log( - ` ${chalk.green("✓")} Gemini CLI GEMINI.md (section written)` + ` ${chalk.green("✓")} Gemini CLI GEMINI.md (section written)`, ); console.log( - ` ${chalk.green("✓")} Cursor .cursor/rules/cve-lite.mdc` + ` ${chalk.green("✓")} Cursor .cursor/rules/cve-lite.mdc`, ); console.log( - ` ${chalk.green("✓")} GitHub Copilot .github/copilot-instructions.md (section written)` + ` ${chalk.green("✓")} GitHub Copilot .github/copilot-instructions.md (section written)`, ); console.log( - "\nCommit these files to your repo to share them with your team." + "\nCommit these files to your repo to share them with your team.", ); } diff --git a/src/types.ts b/src/types.ts index 0e00c707..3c1db540 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,10 @@ -export type SeverityLabel = "none" | "low" | "medium" | "high" | "critical" | "unknown"; +export type SeverityLabel = + | "none" + | "low" + | "medium" + | "high" + | "critical" + | "unknown"; export type PackageRef = { name: string; @@ -50,7 +56,14 @@ export type NpmTransitiveGraph = { }; export type ScanMode = "resolved-lockfile" | "manifest-fallback"; -export type ScanSource = "package-lock" | "npm-shrinkwrap" | "pnpm-lock" | "yarn-lock" | "bun-lock" | "package-json" | "unknown"; +export type ScanSource = + | "package-lock" + | "npm-shrinkwrap" + | "pnpm-lock" + | "yarn-lock" + | "bun-lock" + | "package-json" + | "unknown"; export type ScanInput = { mode: ScanMode; @@ -186,7 +199,11 @@ export type Baseline = { findings: BaselineEntry[]; }; -export type CliCommand = "scan" | "advisories-sync" | "install-skill" | "config"; +export type CliCommand = + | "scan" + | "advisories-sync" + | "install-skill" + | "config"; export type ParsedOptions = { version?: boolean; diff --git a/src/usage/scanner.ts b/src/usage/scanner.ts index 5f9808e4..7dab39d1 100644 --- a/src/usage/scanner.ts +++ b/src/usage/scanner.ts @@ -12,7 +12,8 @@ const ALLOWED_EXTS = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"]); // import 'pkg' // require('pkg') // await import('pkg') -const IMPORT_REQUIRE_REGEX = /(?:(?:import|export)\s+[\w\s{},*]+\s+from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\)|import\s*\(\s*['"]([^'"]+)['"]\s*\))/g; +const IMPORT_REQUIRE_REGEX = + /(?:(?:import|export)\s+[\w\s{},*]+\s+from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\)|import\s*\(\s*['"]([^'"]+)['"]\s*\))/g; function getBareModuleName(importPath: string): string { if (importPath.startsWith(".") || importPath.startsWith("/")) { diff --git a/src/utils/advisory.ts b/src/utils/advisory.ts index 09b1ff26..e810cd16 100644 --- a/src/utils/advisory.ts +++ b/src/utils/advisory.ts @@ -23,7 +23,7 @@ export function isGitSource(pkg: PackageRef): boolean { return ( pkg.resolvedUrl !== undefined && pkg.resolvedUrl !== "" && - GIT_SOURCE_PREFIXES.some(prefix => pkg.resolvedUrl!.startsWith(prefix)) + GIT_SOURCE_PREFIXES.some((prefix) => pkg.resolvedUrl!.startsWith(prefix)) ); } diff --git a/src/utils/array.ts b/src/utils/array.ts index 2824c4a0..471c97c9 100644 --- a/src/utils/array.ts +++ b/src/utils/array.ts @@ -40,6 +40,8 @@ export async function runWithConcurrency( } } - await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker)); + await Promise.all( + Array.from({ length: Math.min(limit, items.length) }, worker), + ); return results; } diff --git a/src/utils/baseline.ts b/src/utils/baseline.ts index ed689b89..72d6042a 100644 --- a/src/utils/baseline.ts +++ b/src/utils/baseline.ts @@ -27,13 +27,17 @@ export function writeBaseline(projectRoot: string, findings: Finding[]): void { const baseline: Baseline = { version: 1, createdAt: new Date().toISOString(), - findings: findings.map(f => ({ + findings: findings.map((f) => ({ name: f.pkg.name, version: f.pkg.version, - advisoryIds: f.vulnerabilities.map(v => v.id), + advisoryIds: f.vulnerabilities.map((v) => v.id), })), }; - fs.writeFileSync(baselinePath(projectRoot), JSON.stringify(baseline, null, 2) + "\n", "utf8"); + fs.writeFileSync( + baselinePath(projectRoot), + JSON.stringify(baseline, null, 2) + "\n", + "utf8", + ); } export function filterNewFindings( @@ -42,7 +46,10 @@ export function filterNewFindings( ): { newFindings: Finding[]; suppressedCount: number } { const baselineMap = new Map>(); for (const entry of baseline.findings) { - baselineMap.set(`${entry.name}@${entry.version}`, new Set(entry.advisoryIds)); + baselineMap.set( + `${entry.name}@${entry.version}`, + new Set(entry.advisoryIds), + ); } const newFindings: Finding[] = []; @@ -55,7 +62,9 @@ export function filterNewFindings( newFindings.push(finding); continue; } - const allKnown = finding.vulnerabilities.every(v => baselineAdvisories.has(v.id)); + const allKnown = finding.vulnerabilities.every((v) => + baselineAdvisories.has(v.id), + ); if (allKnown) { suppressedCount++; } else { diff --git a/src/utils/chalk.ts b/src/utils/chalk.ts index ba347dc4..46bf925f 100644 --- a/src/utils/chalk.ts +++ b/src/utils/chalk.ts @@ -14,10 +14,11 @@ const ansi = { cyan: "\x1b[36m", white: "\x1b[37m", whiteBright: "\x1b[97m", - gray: "\x1b[90m" + gray: "\x1b[90m", }; -const colorEnabled = Boolean(process.stdout.isTTY) && process.env.NO_COLOR !== "1"; +const colorEnabled = + Boolean(process.stdout.isTTY) && process.env.NO_COLOR !== "1"; function paint(code: string, text: string): string { if (!colorEnabled) return text; @@ -25,18 +26,15 @@ function paint(code: string, text: string): string { } export const chalk = { - bold: Object.assign( - (text: string) => paint(ansi.bold, text), - { - cyan: (text: string) => paint(ansi.bold + ansi.cyan, text), - green: (text: string) => paint(ansi.bold + ansi.green, text), - magenta: (text: string) => paint(ansi.bold + ansi.magenta, text), - blue: (text: string) => paint(ansi.bold + ansi.blue, text), - red: (text: string) => paint(ansi.bold + ansi.red, text), - yellow: (text: string) => paint(ansi.bold + ansi.yellow, text), - whiteBright: (text: string) => paint(ansi.bold + ansi.whiteBright, text) - } - ), + bold: Object.assign((text: string) => paint(ansi.bold, text), { + cyan: (text: string) => paint(ansi.bold + ansi.cyan, text), + green: (text: string) => paint(ansi.bold + ansi.green, text), + magenta: (text: string) => paint(ansi.bold + ansi.magenta, text), + blue: (text: string) => paint(ansi.bold + ansi.blue, text), + red: (text: string) => paint(ansi.bold + ansi.red, text), + yellow: (text: string) => paint(ansi.bold + ansi.yellow, text), + whiteBright: (text: string) => paint(ansi.bold + ansi.whiteBright, text), + }), cyan: (text: string) => paint(ansi.cyan, text), green: (text: string) => paint(ansi.green, text), greenBright: (text: string) => paint(ansi.greenBright, text), @@ -48,7 +46,7 @@ export const chalk = { magenta: (text: string) => paint(ansi.magenta, text), gray: (text: string) => paint(ansi.gray, text), white: (text: string) => paint(ansi.white, text), - whiteBright: (text: string) => paint(ansi.whiteBright, text) + whiteBright: (text: string) => paint(ansi.whiteBright, text), }; export function stripAnsi(text: string): string { diff --git a/src/utils/create-pr.ts b/src/utils/create-pr.ts index 0f47eb48..ecdd72f7 100644 --- a/src/utils/create-pr.ts +++ b/src/utils/create-pr.ts @@ -38,9 +38,14 @@ const DEPENDENCY_FILES_TO_STAGE = [ "npm-shrinkwrap.json", ] as const; -export function findingsMeetFailOnThreshold(findings: Finding[], failOn: string): boolean { +export function findingsMeetFailOnThreshold( + findings: Finding[], + failOn: string, +): boolean { const failLevel = normalizeSeverity(failOn); - return findings.some(finding => severityOrder[finding.severity] >= severityOrder[failLevel]); + return findings.some( + (finding) => severityOrder[finding.severity] >= severityOrder[failLevel], + ); } export function defaultFixBranchName(date = new Date()): string { @@ -50,7 +55,10 @@ export function defaultFixBranchName(date = new Date()): string { return `cve-lite/fix-${year}-${month}-${day}`; } -export function buildPullRequestTitle(appliedCount: number, packageNames: string[]): string { +export function buildPullRequestTitle( + appliedCount: number, + packageNames: string[], +): string { const pkgList = packageNames.length <= 3 ? packageNames.join(", ") @@ -58,7 +66,10 @@ export function buildPullRequestTitle(appliedCount: number, packageNames: string return `[CVE-Lite-CLI] fix: upgrade ${pkgList} (${appliedCount} ${pluralize(appliedCount, "vulnerability", "vulnerabilities")} resolved)`; } -export function collectAdvisoryIdsForPackage(findings: Finding[], packageName: string): string[] { +export function collectAdvisoryIdsForPackage( + findings: Finding[], + packageName: string, +): string[] { const ids = new Set(); for (const finding of findings) { if (finding.pkg.name !== packageName) continue; @@ -89,8 +100,12 @@ export function buildPullRequestBody(params: { ]; for (const item of params.fixResult.applied) { - const advisoryIds = collectAdvisoryIdsForPackage(params.findingsBeforeFix, item.package); - const advisoryText = advisoryIds.length > 0 ? advisoryIds.join(", ") : "n/a"; + const advisoryIds = collectAdvisoryIdsForPackage( + params.findingsBeforeFix, + item.package, + ); + const advisoryText = + advisoryIds.length > 0 ? advisoryIds.join(", ") : "n/a"; lines.push(`- **${item.package}**: \`${item.from}\` → \`${item.to}\``); lines.push(` - Advisories: ${advisoryText}`); } @@ -111,8 +126,12 @@ export function buildPullRequestBody(params: { return lines.join("\n"); } -async function runCommand(command: string, args: string[], cwd: string): Promise { - return await new Promise(resolve => { +async function runCommand( + command: string, + args: string[], + cwd: string, +): Promise { + return await new Promise((resolve) => { let stdout = ""; let stderr = ""; const child = spawn(command, args, { @@ -120,26 +139,32 @@ async function runCommand(command: string, args: string[], cwd: string): Promise stdio: ["ignore", "pipe", "pipe"], }); - child.stdout?.on("data", chunk => { + child.stdout?.on("data", (chunk) => { stdout += String(chunk); }); - child.stderr?.on("data", chunk => { + child.stderr?.on("data", (chunk) => { stderr += String(chunk); }); - child.on("error", error => { + child.on("error", (error) => { resolve({ stdout, stderr, status: null, error }); }); - child.on("close", code => { + child.on("close", (code) => { resolve({ stdout, stderr, status: code, error: null }); }); }); } async function assertGitRepository(projectPath: string): Promise { - const result = await runCommand("git", ["rev-parse", "--is-inside-work-tree"], projectPath); + const result = await runCommand( + "git", + ["rev-parse", "--is-inside-work-tree"], + projectPath, + ); if (result.error || result.status !== 0 || result.stdout.trim() !== "true") { - throw new Error("--create-pr requires a git repository. Run this command from your project root."); + throw new Error( + "--create-pr requires a git repository. Run this command from your project root.", + ); } } @@ -160,45 +185,75 @@ async function assertGhAvailable(): Promise { } async function hasAnyChanges(projectPath: string): Promise { - const status = await runCommand("git", ["status", "--porcelain"], projectPath); + const status = await runCommand( + "git", + ["status", "--porcelain"], + projectPath, + ); if (status.error || status.status !== 0) { - throw new Error(`Failed to inspect git status: ${status.stderr.trim() || status.error?.message || "unknown error"}`); + throw new Error( + `Failed to inspect git status: ${status.stderr.trim() || status.error?.message || "unknown error"}`, + ); } return status.stdout.trim().length > 0; } -export async function stageDependencyFilesOnly(projectPath: string): Promise { +export async function stageDependencyFilesOnly( + projectPath: string, +): Promise { for (const filePath of DEPENDENCY_FILES_TO_STAGE) { if (!fs.existsSync(path.join(projectPath, filePath))) { continue; } const add = await runCommand("git", ["add", "--", filePath], projectPath); if (add.error || add.status !== 0) { - throw new Error(`Failed to stage dependency file ${filePath}: ${add.stderr.trim() || add.error?.message || "unknown error"}`); + throw new Error( + `Failed to stage dependency file ${filePath}: ${add.stderr.trim() || add.error?.message || "unknown error"}`, + ); } } } -export async function hasStagedDependencyChanges(projectPath: string): Promise { - const diff = await runCommand("git", ["diff", "--cached", "--name-only"], projectPath); +export async function hasStagedDependencyChanges( + projectPath: string, +): Promise { + const diff = await runCommand( + "git", + ["diff", "--cached", "--name-only"], + projectPath, + ); if (diff.error || diff.status !== 0) { - throw new Error(`Failed to inspect staged changes: ${diff.stderr.trim() || diff.error?.message || "unknown error"}`); + throw new Error( + `Failed to inspect staged changes: ${diff.stderr.trim() || diff.error?.message || "unknown error"}`, + ); } const stagedFiles = diff.stdout .split("\n") - .map(line => line.trim()) + .map((line) => line.trim()) .filter(Boolean); - return stagedFiles.some(filePath => - DEPENDENCY_FILES_TO_STAGE.some(target => filePath === target || filePath.endsWith(`/${target}`)), + return stagedFiles.some((filePath) => + DEPENDENCY_FILES_TO_STAGE.some( + (target) => filePath === target || filePath.endsWith(`/${target}`), + ), ); } -async function branchExists(projectPath: string, branchName: string): Promise { - const result = await runCommand("git", ["rev-parse", "--verify", branchName], projectPath); +async function branchExists( + projectPath: string, + branchName: string, +): Promise { + const result = await runCommand( + "git", + ["rev-parse", "--verify", branchName], + projectPath, + ); return result.status === 0; } -export async function selectAvailableBranchName(projectPath: string, baseName: string): Promise { +export async function selectAvailableBranchName( + projectPath: string, + baseName: string, +): Promise { if (!(await branchExists(projectPath, baseName))) { return baseName; } @@ -208,7 +263,9 @@ export async function selectAvailableBranchName(projectPath: string, baseName: s return candidate; } } - throw new Error(`Failed to allocate a unique branch name based on ${baseName}`); + throw new Error( + `Failed to allocate a unique branch name based on ${baseName}`, + ); } function extractPullRequestUrl(output: string): string | null { @@ -226,7 +283,8 @@ export async function createPullRequestForFixes( branchName: baseBranchName, prUrl: null, skipped: true, - skipReason: "No direct dependency fixes were applied, so no pull request was created.", + skipReason: + "No direct dependency fixes were applied, so no pull request was created.", }; } @@ -238,12 +296,16 @@ export async function createPullRequestForFixes( branchName: baseBranchName, prUrl: null, skipped: true, - skipReason: "No lockfile or manifest changes were detected after applying fixes.", + skipReason: + "No lockfile or manifest changes were detected after applying fixes.", }; } - const packageNames = params.fixResult.applied.map(a => a.package); - const title = buildPullRequestTitle(params.fixResult.appliedFixCount, packageNames); + const packageNames = params.fixResult.applied.map((a) => a.package); + const title = buildPullRequestTitle( + params.fixResult.appliedFixCount, + packageNames, + ); const body = buildPullRequestBody({ fixResult: params.fixResult, findingsBeforeFix: params.findingsBeforeFix, @@ -256,34 +318,68 @@ export async function createPullRequestForFixes( branchName: baseBranchName, prUrl: null, skipped: true, - skipReason: "No dependency manifest or lockfile changes were detected after applying fixes.", + skipReason: + "No dependency manifest or lockfile changes were detected after applying fixes.", }; } - const branchName = await selectAvailableBranchName(params.projectPath, baseBranchName); - const checkout = await runCommand("git", ["checkout", "-b", branchName], params.projectPath); + const branchName = await selectAvailableBranchName( + params.projectPath, + baseBranchName, + ); + const checkout = await runCommand( + "git", + ["checkout", "-b", branchName], + params.projectPath, + ); if (checkout.error || checkout.status !== 0) { - const message = checkout.stderr.trim() || checkout.error?.message || "unknown error"; + const message = + checkout.stderr.trim() || checkout.error?.message || "unknown error"; throw new Error(`Failed to create branch ${branchName}: ${message}`); } - const commit = await runCommand("git", ["commit", "-m", title], params.projectPath); + const commit = await runCommand( + "git", + ["commit", "-m", title], + params.projectPath, + ); if (commit.error || commit.status !== 0) { - throw new Error(`Failed to commit dependency changes: ${commit.stderr.trim() || commit.error?.message || "unknown error"}`); + throw new Error( + `Failed to commit dependency changes: ${commit.stderr.trim() || commit.error?.message || "unknown error"}`, + ); } - const push = await runCommand("git", ["push", "-u", "origin", branchName], params.projectPath); + const push = await runCommand( + "git", + ["push", "-u", "origin", branchName], + params.projectPath, + ); if (push.error || push.status !== 0) { - throw new Error(`Failed to push branch ${branchName}: ${push.stderr.trim() || push.error?.message || "unknown error"}`); + throw new Error( + `Failed to push branch ${branchName}: ${push.stderr.trim() || push.error?.message || "unknown error"}`, + ); } const create = await runCommand( "gh", - ["pr", "create", "--base", params.baseBranch, "--head", branchName, "--title", title, "--body", body], + [ + "pr", + "create", + "--base", + params.baseBranch, + "--head", + branchName, + "--title", + title, + "--body", + body, + ], params.projectPath, ); if (create.error || create.status !== 0) { - throw new Error(`Failed to open pull request: ${create.stderr.trim() || create.error?.message || "unknown error"}`); + throw new Error( + `Failed to open pull request: ${create.stderr.trim() || create.error?.message || "unknown error"}`, + ); } const prUrl = extractPullRequestUrl(`${create.stdout}\n${create.stderr}`); diff --git a/src/utils/file.ts b/src/utils/file.ts index 7f084840..bb3c8a7f 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -15,7 +15,11 @@ export function relativeOrName(rootDir: string, filePath: string): string { return relative || path.basename(filePath); } -export function findFiles(rootDir: string, names: string[], maxDepth: number): string[] { +export function findFiles( + rootDir: string, + names: string[], + maxDepth: number, +): string[] { const results: string[] = []; function walk(currentDir: string, depth: number) { @@ -51,7 +55,10 @@ export function findFiles(rootDir: string, names: string[], maxDepth: number): s }); } -export function findNearestPackageJson(projectRoot: string, maxDepth: number): string | null { +export function findNearestPackageJson( + projectRoot: string, + maxDepth: number, +): string | null { const rootCandidate = path.join(projectRoot, "package.json"); if (fs.existsSync(rootCandidate)) return rootCandidate; const found = findFiles(projectRoot, ["package.json"], maxDepth); diff --git a/src/utils/fix-runner.ts b/src/utils/fix-runner.ts index 032b98d4..559f1e0d 100644 --- a/src/utils/fix-runner.ts +++ b/src/utils/fix-runner.ts @@ -4,7 +4,10 @@ import { chalk } from "./chalk.js"; import type { ParsedOptions } from "../types.js"; import type { SeverityLabel } from "../types.js"; import { pluralize } from "./string.js"; -import type { SuggestedFixCommandPlan, SuggestedFixTarget } from "../remediation/fix-commands.js"; +import type { + SuggestedFixCommandPlan, + SuggestedFixTarget, +} from "../remediation/fix-commands.js"; import type { DebugLogger } from "../output/debug.js"; export function buildFixCommandParts( @@ -12,15 +15,31 @@ export function buildFixCommandParts( targets: SuggestedFixTarget[], ): string[] { if (packageManager === "npm") { - return ["npm", "install", ...targets.map(target => `${target.package}@${target.targetVersion}`)]; + return [ + "npm", + "install", + ...targets.map((target) => `${target.package}@${target.targetVersion}`), + ]; } if (packageManager === "pnpm") { - return ["pnpm", "add", ...targets.map(target => `${target.package}@${target.targetVersion}`)]; + return [ + "pnpm", + "add", + ...targets.map((target) => `${target.package}@${target.targetVersion}`), + ]; } if (packageManager === "bun") { - return ["bun", "add", ...targets.map(target => `${target.package}@${target.targetVersion}`)]; + return [ + "bun", + "add", + ...targets.map((target) => `${target.package}@${target.targetVersion}`), + ]; } - return ["yarn", "add", ...targets.map(target => `${target.package}@${target.targetVersion}`)]; + return [ + "yarn", + "add", + ...targets.map((target) => `${target.package}@${target.targetVersion}`), + ]; } export async function runInstallCommand( @@ -32,7 +51,7 @@ export async function runInstallCommand( const commandLine = [command, ...args].join(" "); debugLog?.("Fix command", { command: commandLine, cwd }); - return await new Promise(resolve => { + return await new Promise((resolve) => { const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"], @@ -41,18 +60,27 @@ export async function runInstallCommand( child.stdout.on("data", () => {}); child.stderr.on("data", () => {}); - child.on("error", error => { - debugLog?.("Fix command finished", { command: commandLine, exitCode: null, error: error.message }); + child.on("error", (error) => { + debugLog?.("Fix command finished", { + command: commandLine, + exitCode: null, + error: error.message, + }); resolve({ status: null, error }); }); - child.on("close", code => { - debugLog?.("Fix command finished", { command: commandLine, exitCode: code }); + child.on("close", (code) => { + debugLog?.("Fix command finished", { + command: commandLine, + exitCode: code, + }); resolve({ status: code, error: null }); }); }); } -export function commandLabelForPackageManager(packageManager: SuggestedFixCommandPlan["packageManager"]): string { +export function commandLabelForPackageManager( + packageManager: SuggestedFixCommandPlan["packageManager"], +): string { if (packageManager === "npm") return "npm install"; if (packageManager === "pnpm") return "pnpm add"; if (packageManager === "bun") return "bun add"; @@ -89,9 +117,15 @@ export async function applyFixesIfRequested(params: { }; } - const directTargets = params.plan.targets.filter(target => target.kind === "direct"); - const transitiveTargets = params.plan.targets.filter(target => target.kind !== "direct"); - const skippedDirect = params.plan.skipped.filter(skip => skip.relationship === "direct" || skip.relationship === "unknown"); + const directTargets = params.plan.targets.filter( + (target) => target.kind === "direct", + ); + const transitiveTargets = params.plan.targets.filter( + (target) => target.kind !== "direct", + ); + const skippedDirect = params.plan.skipped.filter( + (skip) => skip.relationship === "direct" || skip.relationship === "unknown", + ); if (directTargets.length === 0) { const skippedCount = transitiveTargets.length + skippedDirect.length; @@ -105,7 +139,9 @@ export async function applyFixesIfRequested(params: { }; } - const commandLabel = commandLabelForPackageManager(params.plan.packageManager); + const commandLabel = commandLabelForPackageManager( + params.plan.packageManager, + ); const total = directTargets.length; const spinner = createSpinner( `Applying direct package fixes with ${commandLabel}... 0/${total}`, @@ -114,8 +150,12 @@ export async function applyFixesIfRequested(params: { const fixHint = `\nHint: Check that \`${params.plan.packageManager}\` is available and you have write access to node_modules.\nIf running in CI, ensure the install step has already run before cve-lite --fix.`; for (let i = 0; i < directTargets.length; i++) { const target = directTargets[i]; - const commandParts = buildFixCommandParts(params.plan.packageManager, [target]); - spinner.update(`Applying direct fix ${i + 1}/${total}: ${commandParts.join(" ")}`); + const commandParts = buildFixCommandParts(params.plan.packageManager, [ + target, + ]); + spinner.update( + `Applying direct fix ${i + 1}/${total}: ${commandParts.join(" ")}`, + ); const run = await runInstallCommand( commandParts[0], commandParts.slice(1), @@ -128,10 +168,14 @@ export async function applyFixesIfRequested(params: { } if ((run.status ?? 1) !== 0) { spinner.fail(`Fix command failed (${commandLabel})`); - throw new Error(`Fix command exited with status ${run.status ?? 1}${fixHint}`); + throw new Error( + `Fix command exited with status ${run.status ?? 1}${fixHint}`, + ); } } - spinner.succeed(`Applied ${directTargets.length} direct package ${pluralize(directTargets.length, "fix", "fixes")} with ${commandLabel}`); + spinner.succeed( + `Applied ${directTargets.length} direct package ${pluralize(directTargets.length, "fix", "fixes")} with ${commandLabel}`, + ); const skippedCount = transitiveTargets.length + skippedDirect.length; return { @@ -139,7 +183,7 @@ export async function applyFixesIfRequested(params: { skippedCount, skippedTransitiveCount: transitiveTargets.length, skippedNoValidatedTargetCount: skippedDirect.length, - applied: directTargets.map(target => ({ + applied: directTargets.map((target) => ({ package: target.package, from: target.currentVersion ?? "unknown", to: target.targetVersion, @@ -172,14 +216,26 @@ export function printFixModeSummary(params: { console.log(""); console.log(chalk.bold.cyan("Fix summary")); - console.log(`- Applied fixes: ${chalk.green(String(result.appliedFixCount))}`); - console.log(`- Skipped findings: ${chalk.yellow(String(result.skippedCount))}`); + console.log( + `- Applied fixes: ${chalk.green(String(result.appliedFixCount))}`, + ); + console.log( + `- Skipped findings: ${chalk.yellow(String(result.skippedCount))}`, + ); if (result.skippedCount > 0) { - console.log(` - Transitive (v1 skip): ${chalk.yellow(String(result.skippedTransitiveCount))}`); - console.log(` - No validated direct target: ${chalk.yellow(String(result.skippedNoValidatedTargetCount))}`); + console.log( + ` - Transitive (v1 skip): ${chalk.yellow(String(result.skippedTransitiveCount))}`, + ); + console.log( + ` - No validated direct target: ${chalk.yellow(String(result.skippedNoValidatedTargetCount))}`, + ); } - console.log(`- Findings before fix: ${chalk.white(String(params.findingsBeforeFix))}`); - console.log(`- Remaining findings after fix: ${chalk.white(String(params.findingsAfterFix))}`); + console.log( + `- Findings before fix: ${chalk.white(String(params.findingsBeforeFix))}`, + ); + console.log( + `- Remaining findings after fix: ${chalk.white(String(params.findingsAfterFix))}`, + ); console.log( `- Remaining severity mix: critical ${chalk.redBright(String(params.remainingBySeverity.critical))}, high ${chalk.red(String(params.remainingBySeverity.high))}, medium ${chalk.yellow(String(params.remainingBySeverity.medium))}, low ${chalk.blueBright(String(params.remainingBySeverity.low))}, unknown ${chalk.magenta(String(params.remainingBySeverity.unknown))}`, ); diff --git a/src/utils/network.ts b/src/utils/network.ts index 31bb7f90..471c5ece 100644 --- a/src/utils/network.ts +++ b/src/utils/network.ts @@ -1,7 +1,8 @@ export function extractErrorMessage(error: unknown): string { if (!(error instanceof Error)) return String(error); const cause = (error as NodeJS.ErrnoException & { cause?: unknown }).cause; - if (cause instanceof Error) return `${error.message}: ${extractErrorMessage(cause)}`; + if (cause instanceof Error) + return `${error.message}: ${extractErrorMessage(cause)}`; return error.message; } @@ -53,7 +54,9 @@ export function serverAdvisoryRequestHint(): string[] { export function offlineDbSyncHint(offlineDb?: string): string[] { const lines = ["To build it, run: cve-lite advisories sync"]; if (offlineDb) { - lines.push(`Or to save it to the requested path: cve-lite advisories sync --output ${offlineDb}`); + lines.push( + `Or to save it to the requested path: cve-lite advisories sync --output ${offlineDb}`, + ); } return lines; } @@ -100,7 +103,7 @@ export function isSslCertificateError(error: unknown): boolean { const cause = (error as { cause?: unknown }).cause; if (cause) return isSslCertificateError(cause); const normalized = error.message.toLowerCase(); - return SSL_ERROR_MESSAGE_FRAGMENTS.some(f => normalized.includes(f)); + return SSL_ERROR_MESSAGE_FRAGMENTS.some((f) => normalized.includes(f)); } export function isRateLimitError(message: string): boolean { @@ -152,7 +155,7 @@ export function isLikelyBlockedAdvisoryRequestError(message: string): boolean { "unable to verify the first certificate", ]; - if (blockedIndicators.some(indicator => finalCause.includes(indicator))) { + if (blockedIndicators.some((indicator) => finalCause.includes(indicator))) { return true; } diff --git a/src/utils/package-json.ts b/src/utils/package-json.ts index 5374013c..63f4cf33 100644 --- a/src/utils/package-json.ts +++ b/src/utils/package-json.ts @@ -2,7 +2,10 @@ import fs from "node:fs"; import path from "node:path"; import YAML from "yaml"; -export function readDirectDependencyNames(projectPath: string, prodOnly: boolean): Set | null { +export function readDirectDependencyNames( + projectPath: string, + prodOnly: boolean, +): Set | null { const packageJsonPath = path.join(projectPath, "package.json"); if (!fs.existsSync(packageJsonPath)) { return null; @@ -18,7 +21,10 @@ export function readDirectDependencyNames(projectPath: string, prodOnly: boolean addDependencyNamesFromManifest(directNames, rootManifest, prodOnly); const workspacePatterns = readWorkspacePatterns(rootManifest, projectPath); - for (const workspacePackageJsonPath of resolveWorkspacePackageJsonPaths(projectPath, workspacePatterns)) { + for (const workspacePackageJsonPath of resolveWorkspacePackageJsonPaths( + projectPath, + workspacePatterns, + )) { const workspaceManifest = readPackageJsonObject(workspacePackageJsonPath); if (!workspaceManifest) continue; addDependencyNamesFromManifest(directNames, workspaceManifest, prodOnly); @@ -30,10 +36,14 @@ export function readDirectDependencyNames(projectPath: string, prodOnly: boolean } } -function readPackageJsonObject(filePath: string): Record | null { +function readPackageJsonObject( + filePath: string, +): Record | null { try { const raw = JSON.parse(fs.readFileSync(filePath, "utf8")); - return raw && typeof raw === "object" ? raw as Record : null; + return raw && typeof raw === "object" + ? (raw as Record) + : null; } catch { return null; } @@ -46,10 +56,14 @@ function addDependencyNamesFromManifest( ) { const sections: Array | undefined> = [ isRecord(manifest.dependencies) ? manifest.dependencies : undefined, - isRecord(manifest.optionalDependencies) ? manifest.optionalDependencies : undefined, + isRecord(manifest.optionalDependencies) + ? manifest.optionalDependencies + : undefined, ]; if (!prodOnly) { - sections.push(isRecord(manifest.devDependencies) ? manifest.devDependencies : undefined); + sections.push( + isRecord(manifest.devDependencies) ? manifest.devDependencies : undefined, + ); } for (const section of sections) { @@ -60,23 +74,34 @@ function addDependencyNamesFromManifest( } } -function readWorkspacePatterns(manifest: Record, projectPath: string): string[] { +function readWorkspacePatterns( + manifest: Record, + projectPath: string, +): string[] { const workspaces = manifest.workspaces; if (Array.isArray(workspaces)) { - return workspaces.filter((value): value is string => typeof value === "string"); + return workspaces.filter( + (value): value is string => typeof value === "string", + ); } if (isRecord(workspaces) && Array.isArray(workspaces.packages)) { - return workspaces.packages.filter((value): value is string => typeof value === "string"); + return workspaces.packages.filter( + (value): value is string => typeof value === "string", + ); } const pnpmWorkspaceYamlPath = path.join(projectPath, "pnpm-workspace.yaml"); if (fs.existsSync(pnpmWorkspaceYamlPath)) { try { - const parsed = YAML.parse(fs.readFileSync(pnpmWorkspaceYamlPath, "utf8")) as any; + const parsed = YAML.parse( + fs.readFileSync(pnpmWorkspaceYamlPath, "utf8"), + ) as any; const packages = parsed?.packages; if (Array.isArray(packages)) { - return packages.filter((value): value is string => typeof value === "string"); + return packages.filter( + (value): value is string => typeof value === "string", + ); } } catch { // ignore @@ -86,15 +111,26 @@ function readWorkspacePatterns(manifest: Record, projectPath: s return []; } -function resolveWorkspacePackageJsonPaths(projectPath: string, patterns: string[]): string[] { +function resolveWorkspacePackageJsonPaths( + projectPath: string, + patterns: string[], +): string[] { const matches = new Set(); for (const pattern of patterns) { const normalized = pattern.replace(/\\/g, "/").replace(/\/+$/, ""); if (!normalized) continue; - for (const relativeDir of expandWorkspacePattern(projectPath, normalized.split("/").filter(Boolean), "")) { - const packageJsonPath = path.join(projectPath, relativeDir, "package.json"); + for (const relativeDir of expandWorkspacePattern( + projectPath, + normalized.split("/").filter(Boolean), + "", + )) { + const packageJsonPath = path.join( + projectPath, + relativeDir, + "package.json", + ); if (fs.existsSync(packageJsonPath)) { matches.add(packageJsonPath); } @@ -104,14 +140,20 @@ function resolveWorkspacePackageJsonPaths(projectPath: string, patterns: string[ return [...matches]; } -function expandWorkspacePattern(projectPath: string, segments: string[], currentRelativePath: string): string[] { +function expandWorkspacePattern( + projectPath: string, + segments: string[], + currentRelativePath: string, +): string[] { if (segments.length === 0) { return currentRelativePath ? [currentRelativePath] : []; } const [segment, ...rest] = segments; if (segment === "*") { - const baseDir = currentRelativePath ? path.join(projectPath, currentRelativePath) : projectPath; + const baseDir = currentRelativePath + ? path.join(projectPath, currentRelativePath) + : projectPath; let entries: fs.Dirent[] = []; try { entries = fs.readdirSync(baseDir, { withFileTypes: true }); @@ -120,8 +162,8 @@ function expandWorkspacePattern(projectPath: string, segments: string[], current } return entries - .filter(entry => entry.isDirectory()) - .flatMap(entry => { + .filter((entry) => entry.isDirectory()) + .flatMap((entry) => { const nextRelativePath = currentRelativePath ? path.join(currentRelativePath, entry.name) : entry.name; @@ -129,9 +171,14 @@ function expandWorkspacePattern(projectPath: string, segments: string[], current }); } - const nextRelativePath = currentRelativePath ? path.join(currentRelativePath, segment) : segment; + const nextRelativePath = currentRelativePath + ? path.join(currentRelativePath, segment) + : segment; const nextAbsolutePath = path.join(projectPath, nextRelativePath); - if (!fs.existsSync(nextAbsolutePath) || !fs.statSync(nextAbsolutePath).isDirectory()) { + if ( + !fs.existsSync(nextAbsolutePath) || + !fs.statSync(nextAbsolutePath).isDirectory() + ) { return []; } diff --git a/src/utils/path-coverage.ts b/src/utils/path-coverage.ts index 6a5ac88f..bb018490 100644 --- a/src/utils/path-coverage.ts +++ b/src/utils/path-coverage.ts @@ -8,8 +8,11 @@ export function calculatePathCoverage( knownPaths: string[][], coveredPath: string[] | null | undefined, ): PathCoverage { - const coveredPaths = coveredPath && coveredPath.length > 0 ? [coveredPath] : []; - const remainingPaths = knownPaths.filter(path => !coveredPaths.some(covered => pathsEqual(path, covered))); + const coveredPaths = + coveredPath && coveredPath.length > 0 ? [coveredPath] : []; + const remainingPaths = knownPaths.filter( + (path) => !coveredPaths.some((covered) => pathsEqual(path, covered)), + ); return { coverage: remainingPaths.length === 0 ? "complete" : "partial", diff --git a/src/utils/severity.ts b/src/utils/severity.ts index cd99f42d..a30ecb90 100644 --- a/src/utils/severity.ts +++ b/src/utils/severity.ts @@ -1,6 +1,8 @@ import type { SeverityLabel } from "../types.js"; -export function countBySeverity(findings: T[]): Record { +export function countBySeverity( + findings: T[], +): Record { const counts: Record = { none: 0, low: 0, @@ -15,7 +17,9 @@ export function countBySeverity(findings: return counts; } -export function severityToSarifLevel(severity: SeverityLabel): "error" | "warning" | "note" { +export function severityToSarifLevel( + severity: SeverityLabel, +): "error" | "warning" | "note" { if (severity === "critical" || severity === "high") return "error"; if (severity === "medium") return "warning"; return "note"; diff --git a/src/utils/string.ts b/src/utils/string.ts index ca2ace1d..b2c28eba 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -1,3 +1,7 @@ -export function pluralize(count: number, singular: string, plural = `${singular}s`): string { +export function pluralize( + count: number, + singular: string, + plural = `${singular}s`, +): string { return count === 1 ? singular : plural; } diff --git a/src/utils/update-check.ts b/src/utils/update-check.ts index 227604eb..14d8c893 100644 --- a/src/utils/update-check.ts +++ b/src/utils/update-check.ts @@ -24,7 +24,7 @@ export function isNewer(latest: string, current: string): boolean { const parse = (v: string): number[] => { const core = v.replace(/^v/, "").split("-")[0]; const parts = core.split(".").map(Number); - return parts.length === 3 && parts.every(n => !isNaN(n)) ? parts : []; + return parts.length === 3 && parts.every((n) => !isNaN(n)) ? parts : []; }; const l = parse(latest); const c = parse(current); @@ -35,7 +35,9 @@ export function isNewer(latest: string, current: string): boolean { } function getCacheFilePath(cacheDir?: string): string { - const dir = cacheDir ? path.resolve(cacheDir) : path.join(os.homedir(), ".cache", "cve-lite"); + const dir = cacheDir + ? path.resolve(cacheDir) + : path.join(os.homedir(), ".cache", "cve-lite"); fs.mkdirSync(dir, { recursive: true }); return path.join(dir, "update-check.json"); } @@ -44,8 +46,14 @@ export function readCache(cacheDir?: string): UpdateCache | null { try { const raw = fs.readFileSync(getCacheFilePath(cacheDir), "utf8"); const parsed = JSON.parse(raw) as Record; - if (typeof parsed.latestVersion === "string" && typeof parsed.checkedAt === "string") { - return { latestVersion: parsed.latestVersion, checkedAt: parsed.checkedAt }; + if ( + typeof parsed.latestVersion === "string" && + typeof parsed.checkedAt === "string" + ) { + return { + latestVersion: parsed.latestVersion, + checkedAt: parsed.checkedAt, + }; } return null; } catch { @@ -58,7 +66,7 @@ export function writeCache(latestVersion: string, cacheDir?: string): void { fs.writeFileSync( getCacheFilePath(cacheDir), JSON.stringify({ latestVersion, checkedAt: new Date().toISOString() }), - "utf8" + "utf8", ); } catch { // silently ignore write failures @@ -71,7 +79,7 @@ export function isCacheStale(cache: UpdateCache): boolean { function fetchAndUpdateCache(cacheDir?: string): void { void fetch(NPM_REGISTRY_URL) - .then(res => { + .then((res) => { if (!res.ok) return undefined; return res.json() as Promise; }) @@ -91,7 +99,8 @@ function fetchAndUpdateCache(cacheDir?: string): void { function renderBox(current: string, latest: string): string { const top = chalk.yellow("╭" + "─".repeat(BOX_INNER_WIDTH) + "╮"); - const empty = chalk.yellow("│") + " ".repeat(BOX_INNER_WIDTH) + chalk.yellow("│"); + const empty = + chalk.yellow("│") + " ".repeat(BOX_INNER_WIDTH) + chalk.yellow("│"); const bottom = chalk.yellow("╰" + "─".repeat(BOX_INNER_WIDTH) + "╯"); const line1 = ` ${chalk.bold.yellow("Update available!")} ${chalk.gray(current)} ${chalk.yellow("→")} ${chalk.green(latest)}`; @@ -99,7 +108,12 @@ function renderBox(current: string, latest: string): string { const padLine = (content: string): string => { const visual = stripAnsi(content).length; - return chalk.yellow("│") + content + " ".repeat(Math.max(0, BOX_INNER_WIDTH - visual)) + chalk.yellow("│"); + return ( + chalk.yellow("│") + + content + + " ".repeat(Math.max(0, BOX_INNER_WIDTH - visual)) + + chalk.yellow("│") + ); }; return [top, empty, padLine(line1), padLine(line2), empty, bottom].join("\n"); diff --git a/src/utils/version.ts b/src/utils/version.ts index b09c82e2..cc0b19c1 100644 --- a/src/utils/version.ts +++ b/src/utils/version.ts @@ -3,8 +3,12 @@ export function looksLikeVersion(value: string): boolean { } export function compareVersions(a: string, b: string): number { - const pa = a.split(/[.+-]/).map(n => Number.isFinite(Number(n)) ? Number(n) : n); - const pb = b.split(/[.+-]/).map(n => Number.isFinite(Number(n)) ? Number(n) : n); + const pa = a + .split(/[.+-]/) + .map((n) => (Number.isFinite(Number(n)) ? Number(n) : n)); + const pb = b + .split(/[.+-]/) + .map((n) => (Number.isFinite(Number(n)) ? Number(n) : n)); const len = Math.max(pa.length, pb.length); for (let i = 0; i < len; i++) { const av = pa[i] ?? 0; @@ -29,7 +33,9 @@ export function parseExactManifestVersion(spec: string): string | null { export function isMajorVersionBump(from: string, to: string): boolean { const fromMajor = Number(from.split(".")[0]); const toMajor = Number(to.split(".")[0]); - return !Number.isNaN(fromMajor) && !Number.isNaN(toMajor) && toMajor > fromMajor; + return ( + !Number.isNaN(fromMajor) && !Number.isNaN(toMajor) && toMajor > fromMajor + ); } export function isPreReleaseVersion(version: string): boolean { @@ -38,8 +44,12 @@ export function isPreReleaseVersion(version: string): boolean { export function normalizeRawVersion(value: unknown): string | null { if (typeof value !== "string") return null; - const cleaned = value.replace(/^workspace:/, "").replace(/^npm:/, "").trim(); - if (!cleaned || cleaned.startsWith(".") || cleaned.startsWith("..")) return null; + const cleaned = value + .replace(/^workspace:/, "") + .replace(/^npm:/, "") + .trim(); + if (!cleaned || cleaned.startsWith(".") || cleaned.startsWith("..")) + return null; if (looksLikeVersion(cleaned)) return cleaned; return null; } diff --git a/tests/baseline.test.ts b/tests/baseline.test.ts index f5ea50b4..fcbfc509 100644 --- a/tests/baseline.test.ts +++ b/tests/baseline.test.ts @@ -1,7 +1,11 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { readBaseline, writeBaseline, filterNewFindings } from "../src/utils/baseline.js"; +import { + readBaseline, + writeBaseline, + filterNewFindings, +} from "../src/utils/baseline.js"; import type { Finding } from "../src/types.js"; function makeTempDir(): string { @@ -12,10 +16,18 @@ function removeDir(dir: string): void { fs.rmSync(dir, { recursive: true, force: true }); } -function makeFinding(name: string, version: string, advisoryIds: string[]): Finding { +function makeFinding( + name: string, + version: string, + advisoryIds: string[], +): Finding { return { pkg: { name, version, ecosystem: "npm" }, - vulnerabilities: advisoryIds.map(id => ({ id, aliases: [], summary: "" })), + vulnerabilities: advisoryIds.map((id) => ({ + id, + aliases: [], + summary: "", + })), severity: "high", cveAliases: [], dependencyPaths: [["project", name]], @@ -39,7 +51,9 @@ describe("baseline utilities", () => { try { const findings = [makeFinding("lodash", "4.17.20", ["GHSA-xxx"])]; writeBaseline(dir, findings); - expect(fs.existsSync(path.join(dir, ".cve-lite", "baseline.json"))).toBe(true); + expect(fs.existsSync(path.join(dir, ".cve-lite", "baseline.json"))).toBe( + true, + ); } finally { removeDir(dir); } @@ -54,7 +68,11 @@ describe("baseline utilities", () => { expect(baseline).not.toBeNull(); expect(baseline!.version).toBe(1); expect(baseline!.findings).toHaveLength(1); - expect(baseline!.findings[0]).toEqual({ name: "lodash", version: "4.17.20", advisoryIds: ["GHSA-xxx"] }); + expect(baseline!.findings[0]).toEqual({ + name: "lodash", + version: "4.17.20", + advisoryIds: ["GHSA-xxx"], + }); } finally { removeDir(dir); } @@ -79,7 +97,10 @@ describe("baseline utilities", () => { const findings = [makeFinding("lodash", "4.17.20", ["GHSA-xxx"])]; writeBaseline(dir, findings); const baseline = readBaseline(dir)!; - const { newFindings, suppressedCount } = filterNewFindings(findings, baseline); + const { newFindings, suppressedCount } = filterNewFindings( + findings, + baseline, + ); expect(newFindings).toHaveLength(0); expect(suppressedCount).toBe(1); } finally { @@ -93,7 +114,10 @@ describe("baseline utilities", () => { writeBaseline(dir, [makeFinding("lodash", "4.17.20", ["GHSA-xxx"])]); const baseline = readBaseline(dir)!; const newPkg = makeFinding("axios", "0.21.1", ["GHSA-yyy"]); - const { newFindings, suppressedCount } = filterNewFindings([newPkg], baseline); + const { newFindings, suppressedCount } = filterNewFindings( + [newPkg], + baseline, + ); expect(newFindings).toHaveLength(1); expect(suppressedCount).toBe(0); } finally { @@ -106,8 +130,14 @@ describe("baseline utilities", () => { try { writeBaseline(dir, [makeFinding("lodash", "4.17.20", ["GHSA-xxx"])]); const baseline = readBaseline(dir)!; - const findingWithNewAdvisory = makeFinding("lodash", "4.17.20", ["GHSA-xxx", "GHSA-new"]); - const { newFindings, suppressedCount } = filterNewFindings([findingWithNewAdvisory], baseline); + const findingWithNewAdvisory = makeFinding("lodash", "4.17.20", [ + "GHSA-xxx", + "GHSA-new", + ]); + const { newFindings, suppressedCount } = filterNewFindings( + [findingWithNewAdvisory], + baseline, + ); expect(newFindings).toHaveLength(1); expect(suppressedCount).toBe(0); } finally { @@ -118,10 +148,18 @@ describe("baseline utilities", () => { it("filterNewFindings suppresses when all advisory IDs are in baseline", () => { const dir = makeTempDir(); try { - writeBaseline(dir, [makeFinding("lodash", "4.17.20", ["GHSA-xxx", "GHSA-yyy"])]); + writeBaseline(dir, [ + makeFinding("lodash", "4.17.20", ["GHSA-xxx", "GHSA-yyy"]), + ]); const baseline = readBaseline(dir)!; - const finding = makeFinding("lodash", "4.17.20", ["GHSA-xxx", "GHSA-yyy"]); - const { newFindings, suppressedCount } = filterNewFindings([finding], baseline); + const finding = makeFinding("lodash", "4.17.20", [ + "GHSA-xxx", + "GHSA-yyy", + ]); + const { newFindings, suppressedCount } = filterNewFindings( + [finding], + baseline, + ); expect(newFindings).toHaveLength(0); expect(suppressedCount).toBe(1); } finally { diff --git a/tests/classifier.test.ts b/tests/classifier.test.ts index 29e9efa4..467118ff 100644 --- a/tests/classifier.test.ts +++ b/tests/classifier.test.ts @@ -8,7 +8,10 @@ import type { Finding, PackageRef } from "../src/types.js"; // transitive dep (at a different version), the transitive version must not // receive a direct fix command. -function makeFinding(pkg: PackageRef, overrides: Partial = {}): Finding { +function makeFinding( + pkg: PackageRef, + overrides: Partial = {}, +): Finding { return { pkg, vulnerabilities: [{ id: "OSV-test-001" }], @@ -62,7 +65,7 @@ describe("classifyRelationship — same package name, different versions", () => // No direct install command — uuid@8.3.2 is transitive expect(plan?.command).not.toBe("npm install uuid@9.0.0"); - expect(plan?.targets.find(t => t.kind === "direct")).toBeUndefined(); + expect(plan?.targets.find((t) => t.kind === "direct")).toBeUndefined(); }); it("classifies direct version as direct when it has a path of length 2", () => { @@ -90,17 +93,19 @@ describe("classifyRelationship — same package name, different versions", () => }); const plan = buildSuggestedFixCommandPlan([directFinding], scanInput); - expect(plan?.targets.find(t => t.kind === "direct")?.package).toBe("uuid"); + expect(plan?.targets.find((t) => t.kind === "direct")?.package).toBe( + "uuid", + ); }); it("wrong-parent fixture — js-cookie is transitive and produces a lockfile refresh, not npm install", () => { const scanInput = loadPackages("examples/wrong-parent", false, 4); const jsCookie = scanInput.packages.find( - p => p.name === "js-cookie" && p.version === "3.0.6" + (p) => p.name === "js-cookie" && p.version === "3.0.6", ); expect(jsCookie).toBeDefined(); - expect(jsCookie?.paths?.every(p => p.length > 2)).toBe(true); + expect(jsCookie?.paths?.every((p) => p.length > 2)).toBe(true); const finding = makeFinding(jsCookie!, { relationship: "transitive", @@ -118,7 +123,7 @@ describe("classifyRelationship — same package name, different versions", () => const plan = buildSuggestedFixCommandPlan([finding], scanInput); expect(plan?.command).toBe("npm update js-cookie"); - expect(plan?.targets.find(t => t.kind === "parent-update")).toBeDefined(); - expect(plan?.targets.find(t => t.kind === "direct")).toBeUndefined(); + expect(plan?.targets.find((t) => t.kind === "parent-update")).toBeDefined(); + expect(plan?.targets.find((t) => t.kind === "direct")).toBeUndefined(); }); }); diff --git a/tests/cli-integration.test.ts b/tests/cli-integration.test.ts index 6f59eecf..e65181a9 100644 --- a/tests/cli-integration.test.ts +++ b/tests/cli-integration.test.ts @@ -35,7 +35,9 @@ const writeHtmlReportMock = jest.fn(); const installSkillMock = jest.fn(); const writeSarifReportMock = jest.fn(() => "cve-lite-scan-test.sarif"); const deriveLockfileUriMock = jest.fn(() => "package-lock.json"); -const writeOutputsMock = jest.fn<() => Promise>().mockResolvedValue(undefined); +const writeOutputsMock = jest + .fn<() => Promise>() + .mockResolvedValue(undefined); const hasRootLockfileMock = jest.fn(() => true); const findNestedLockfilesMock = jest.fn(() => []); const handleMultiFolderScanMock = jest.fn(); @@ -57,23 +59,33 @@ jest.unstable_mockModule("../src/parsers/index.js", () => ({ jest.unstable_mockModule("../src/scanner.js", () => ({ scanPackages: scanPackagesMock, buildCoverageNotes: jest.fn(() => ["Coverage note"]), - createAdvisorySource: jest.fn((options?: { osvUrl?: string; offline?: boolean; offlineDb?: string }) => ({ - advisorySource: { - queryBatch: jest.fn(), - getVuln: jest.fn(), - }, - offline: !!options?.offline || !!options?.offlineDb, - sourceLabel: options?.offline || options?.offlineDb - ? `local advisory database (${options?.offlineDb ?? "/tmp/default-advisories.db"})` - : options?.osvUrl - ? `custom OSV endpoint (${options.osvUrl})` - : "OSV (https://api.osv.dev)", - advisoryDbMetadata: options?.offline || options?.offlineDb - ? { lastSyncAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), sourceUrl: "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip" } - : null, - advisoryDbIsStale: false, - cleanup: jest.fn(), - })), + createAdvisorySource: jest.fn( + (options?: { osvUrl?: string; offline?: boolean; offlineDb?: string }) => ({ + advisorySource: { + queryBatch: jest.fn(), + getVuln: jest.fn(), + }, + offline: !!options?.offline || !!options?.offlineDb, + sourceLabel: + options?.offline || options?.offlineDb + ? `local advisory database (${options?.offlineDb ?? "/tmp/default-advisories.db"})` + : options?.osvUrl + ? `custom OSV endpoint (${options.osvUrl})` + : "OSV (https://api.osv.dev)", + advisoryDbMetadata: + options?.offline || options?.offlineDb + ? { + lastSyncAt: new Date( + Date.now() - 2 * 60 * 60 * 1000, + ).toISOString(), + sourceUrl: + "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", + } + : null, + advisoryDbIsStale: false, + cleanup: jest.fn(), + }), + ), })); jest.unstable_mockModule("../src/advisory/osv-sync.js", () => ({ @@ -176,7 +188,12 @@ function createFinding(overrides?: Partial): Finding { } const multerDirectFixFinding = createFinding({ - pkg: { name: "multer", version: "1.4.5-lts.2", ecosystem: "npm", paths: [["project", "multer"]] }, + pkg: { + name: "multer", + version: "1.4.5-lts.2", + ecosystem: "npm", + paths: [["project", "multer"]], + }, }); const npmFixPlan = { @@ -199,8 +216,10 @@ const npmFixPlan = { skipped: [], }; -const NPM_FIX_HINT = "Check that `npm` is available and you have write access to node_modules."; -const CI_FIX_HINT = "If running in CI, ensure the install step has already run before cve-lite --fix."; +const NPM_FIX_HINT = + "Check that `npm` is available and you have write access to node_modules."; +const CI_FIX_HINT = + "If running in CI, ensure the install step has already run before cve-lite --fix."; function setupFixModeWithNpmDirectTarget() { parseArgsMock.mockReturnValue({ @@ -225,8 +244,14 @@ function setupFixModeWithNpmDirectTarget() { buildSuggestedFixCommandPlanMock.mockReturnValue(npmFixPlan); } -function createMockSpawnChild(): EventEmitter & { stdout: PassThrough; stderr: PassThrough } { - const child = new EventEmitter() as EventEmitter & { stdout: PassThrough; stderr: PassThrough }; +function createMockSpawnChild(): EventEmitter & { + stdout: PassThrough; + stderr: PassThrough; +} { + const child = new EventEmitter() as EventEmitter & { + stdout: PassThrough; + stderr: PassThrough; + }; child.stdout = new PassThrough(); child.stderr = new PassThrough(); return child; @@ -241,13 +266,17 @@ async function runIndexModule() { try { await import(`../src/index.ts?test=${Date.now()}-${Math.random()}`); - await new Promise(resolve => setTimeout(resolve, 0)); + await new Promise((resolve) => setTimeout(resolve, 0)); } finally { } - const exitCalls = exitSpy.mock.calls.map(call => call[0]); - const stdout = logSpy.mock.calls.map(call => call.map(value => String(value)).join(" ")); - const stderr = errorSpy.mock.calls.map(call => call.map(value => String(value)).join(" ")); + const exitCalls = exitSpy.mock.calls.map((call) => call[0]); + const stdout = logSpy.mock.calls.map((call) => + call.map((value) => String(value)).join(" "), + ); + const stderr = errorSpy.mock.calls.map((call) => + call.map((value) => String(value)).join(" "), + ); exitSpy.mockRestore(); logSpy.mockRestore(); @@ -270,7 +299,10 @@ describe("CLI integration", () => { jest.clearAllMocks(); buildSuggestedFixCommandPlanMock.mockReturnValue(null); spawnMock.mockImplementation(() => { - const child = new EventEmitter() as EventEmitter & { stdout: PassThrough; stderr: PassThrough }; + const child = new EventEmitter() as EventEmitter & { + stdout: PassThrough; + stderr: PassThrough; + }; child.stdout = new PassThrough(); child.stderr = new PassThrough(); setImmediate(() => child.emit("close", 0)); @@ -286,26 +318,36 @@ describe("CLI integration", () => { }, projectArg: ".", }); - buildNoPackagesMessageMock.mockReturnValue("No scannable packages were found."); + buildNoPackagesMessageMock.mockReturnValue( + "No scannable packages were found.", + ); loadPackagesMock.mockReturnValue(createScanInput()); scanPackagesMock.mockResolvedValue([]); syncOsvAdvisoriesMock.mockResolvedValue({ advisoryCount: 0, dbPath: "/tmp/advisories.db", - sourceUrl: "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", + sourceUrl: + "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", }); serializeFindingMock.mockImplementation((finding: Finding) => ({ package: finding.pkg.name, severity: finding.severity, })); buildReportDataMock.mockReturnValue({ cliVersion: "1.8.0", findings: [] }); - writeHtmlReportMock.mockResolvedValue({ reportPath: "/tmp/cve-report/index.html" }); + writeHtmlReportMock.mockResolvedValue({ + reportPath: "/tmp/cve-report/index.html", + }); installSkillMock.mockReturnValue(undefined); }); it("returns a json payload and exits successfully when no findings are present", async () => { const packages: PackageRef[] = [ - { name: "lodash", version: "4.17.21", ecosystem: "npm", paths: [["project", "lodash"]] }, + { + name: "lodash", + version: "4.17.21", + ecosystem: "npm", + paths: [["project", "lodash"]], + }, ]; const parsedOptions = { json: true, @@ -328,7 +370,10 @@ describe("CLI integration", () => { expect(writeOutputsMock).toHaveBeenCalledWith( expect.objectContaining({ json: true }), expect.objectContaining({ sorted: [], suggestedFixCommands: null }), - expect.objectContaining({ mode: "manifest-fallback", source: "package-json" }), + expect.objectContaining({ + mode: "manifest-fallback", + source: "package-json", + }), expect.any(String), ); expect(printCompactOutputMock).not.toHaveBeenCalled(); @@ -357,7 +402,9 @@ describe("CLI integration", () => { }); it("includes child workspace dependency names in direct classification context", async () => { - const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), "cve-lite-workspace-context-")); + const projectDir = fs.mkdtempSync( + path.join(os.tmpdir(), "cve-lite-workspace-context-"), + ); const clientDir = path.join(projectDir, "packages", "client"); const experienceDir = path.join(projectDir, "packages", "experience"); @@ -418,9 +465,13 @@ describe("CLI integration", () => { expect(result.exitCode).toBe(0); expect(scanPackagesMock).toHaveBeenCalled(); - const context = scanPackagesMock.mock.calls[0]?.[3] as { directDependencyNames?: Set } | undefined; + const context = scanPackagesMock.mock.calls[0]?.[3] as + | { directDependencyNames?: Set } + | undefined; expect(context?.directDependencyNames?.has("husky")).toBe(true); - expect(context?.directDependencyNames?.has("@eslint/eslintrc")).toBe(true); + expect(context?.directDependencyNames?.has("@eslint/eslintrc")).toBe( + true, + ); } finally { fs.rmSync(projectDir, { recursive: true, force: true }); } @@ -456,7 +507,9 @@ describe("CLI integration", () => { expect(result.exitCode).toBe(1); expect(result.stderr.join("\n")).toContain("Error: Unknown option: --wat"); - expect(result.stderr.join("\n")).toContain("Run `cve-lite --help` to see supported options."); + expect(result.stderr.join("\n")).toContain( + "Run `cve-lite --help` to see supported options.", + ); expect(printBannerMock).not.toHaveBeenCalled(); expect(loadPackagesMock).not.toHaveBeenCalled(); }); @@ -474,7 +527,9 @@ describe("CLI integration", () => { }, projectArg: ".", }); - loadPackagesMock.mockReturnValue(createScanInput({ packages: [finding.pkg] })); + loadPackagesMock.mockReturnValue( + createScanInput({ packages: [finding.pkg] }), + ); scanPackagesMock.mockResolvedValue([finding]); const result = await runIndexModule(); @@ -482,7 +537,10 @@ describe("CLI integration", () => { expect(result.exitCode).toBe(1); expect(writeOutputsMock).toHaveBeenCalledWith( expect.objectContaining({ json: true }), - expect.objectContaining({ sorted: [finding], suggestedFixCommands: null }), + expect.objectContaining({ + sorted: [finding], + suggestedFixCommands: null, + }), expect.any(Object), expect.any(String), ); @@ -494,7 +552,10 @@ describe("CLI integration", () => { const result = await runIndexModule(); expect(result.exitCode).toBe(0); - expect(logWarnMock).toHaveBeenCalledWith("No scannable packages were found.", expect.anything()); + expect(logWarnMock).toHaveBeenCalledWith( + "No scannable packages were found.", + expect.anything(), + ); }); it("fails fast for an invalid osv url", async () => { @@ -513,14 +574,25 @@ describe("CLI integration", () => { const result = await runIndexModule(); expect(result.exitCode).toBe(1); - expect(result.stderr.join("\n")).toContain("Invalid value for --osv-url: not-a-url"); + expect(result.stderr.join("\n")).toContain( + "Invalid value for --osv-url: not-a-url", + ); expect(loadPackagesMock).not.toHaveBeenCalled(); }); it("prints an offline advisory DB hint when OSV requests appear blocked", async () => { - loadPackagesMock.mockReturnValue(createScanInput({ - packages: [{ name: "lodash", version: "4.17.21", ecosystem: "npm", paths: [["project", "lodash"]] }], - })); + loadPackagesMock.mockReturnValue( + createScanInput({ + packages: [ + { + name: "lodash", + version: "4.17.21", + ecosystem: "npm", + paths: [["project", "lodash"]], + }, + ], + }), + ); scanPackagesMock.mockRejectedValue( new Error("OSV batch query failed for https://api.osv.dev: fetch failed"), ); @@ -529,48 +601,90 @@ describe("CLI integration", () => { const stderr = stripAnsi(result.stderr.join("\n")); expect(result.exitCode).toBe(1); - expect(stderr).toContain("Error: OSV batch query failed for https://api.osv.dev: fetch failed"); - expect(stderr).toContain("Hint: Outbound access to the OSV API may be blocked or restricted in this environment."); - expect(stderr).toContain("build the advisory DB on a machine with OSV access"); - expect(stderr).toContain("cve-lite advisories sync --output /path/to/advisories.db"); + expect(stderr).toContain( + "Error: OSV batch query failed for https://api.osv.dev: fetch failed", + ); + expect(stderr).toContain( + "Hint: Outbound access to the OSV API may be blocked or restricted in this environment.", + ); + expect(stderr).toContain( + "build the advisory DB on a machine with OSV access", + ); + expect(stderr).toContain( + "cve-lite advisories sync --output /path/to/advisories.db", + ); }); it("prints a retry and offline hint when OSV rate limits the scan", async () => { - loadPackagesMock.mockReturnValue(createScanInput({ - packages: [{ name: "lodash", version: "4.17.21", ecosystem: "npm", paths: [["project", "lodash"]] }], - })); + loadPackagesMock.mockReturnValue( + createScanInput({ + packages: [ + { + name: "lodash", + version: "4.17.21", + ecosystem: "npm", + paths: [["project", "lodash"]], + }, + ], + }), + ); scanPackagesMock.mockRejectedValue( - new Error("OSV batch query failed for https://api.osv.dev: OSV batch query failed: 429 Too Many Requests"), + new Error( + "OSV batch query failed for https://api.osv.dev: OSV batch query failed: 429 Too Many Requests", + ), ); const result = await runIndexModule(); const stderr = stripAnsi(result.stderr.join("\n")); expect(result.exitCode).toBe(1); - expect(stderr).toContain("Error: OSV batch query failed for https://api.osv.dev: OSV batch query failed: 429 Too Many Requests"); - expect(stderr).toContain("Hint: OSV API rate limit reached. Wait a moment and retry, or scan offline:"); + expect(stderr).toContain( + "Error: OSV batch query failed for https://api.osv.dev: OSV batch query failed: 429 Too Many Requests", + ); + expect(stderr).toContain( + "Hint: OSV API rate limit reached. Wait a moment and retry, or scan offline:", + ); expect(stderr).toContain("cve-lite . --offline"); expect(stderr).toContain("cve-lite advisories sync"); - expect(stderr).not.toContain("Outbound access to the OSV API may be blocked"); + expect(stderr).not.toContain( + "Outbound access to the OSV API may be blocked", + ); }); it("prints a transient service hint when OSV returns a server error", async () => { - loadPackagesMock.mockReturnValue(createScanInput({ - packages: [{ name: "lodash", version: "4.17.21", ecosystem: "npm", paths: [["project", "lodash"]] }], - })); + loadPackagesMock.mockReturnValue( + createScanInput({ + packages: [ + { + name: "lodash", + version: "4.17.21", + ecosystem: "npm", + paths: [["project", "lodash"]], + }, + ], + }), + ); scanPackagesMock.mockRejectedValue( - new Error("OSV batch query failed for https://api.osv.dev: OSV batch query failed: 503 Service Unavailable"), + new Error( + "OSV batch query failed for https://api.osv.dev: OSV batch query failed: 503 Service Unavailable", + ), ); const result = await runIndexModule(); const stderr = stripAnsi(result.stderr.join("\n")); expect(result.exitCode).toBe(1); - expect(stderr).toContain("Error: OSV batch query failed for https://api.osv.dev: OSV batch query failed: 503 Service Unavailable"); - expect(stderr).toContain("Hint: OSV API may be temporarily unavailable. Wait a moment and retry, or scan offline:"); + expect(stderr).toContain( + "Error: OSV batch query failed for https://api.osv.dev: OSV batch query failed: 503 Service Unavailable", + ); + expect(stderr).toContain( + "Hint: OSV API may be temporarily unavailable. Wait a moment and retry, or scan offline:", + ); expect(stderr).toContain("cve-lite . --offline"); expect(stderr).toContain("cve-lite advisories sync"); - expect(stderr).not.toContain("Outbound access to the OSV API may be blocked"); + expect(stderr).not.toContain( + "Outbound access to the OSV API may be blocked", + ); }); it("routes verbose mode through the detailed printer pipeline", async () => { @@ -599,11 +713,19 @@ describe("CLI integration", () => { const result = await runIndexModule(); expect(result.exitCode).toBe(0); - expect(logWarnMock).toHaveBeenCalledWith("Manifest fallback warning", expect.anything()); + expect(logWarnMock).toHaveBeenCalledWith( + "Manifest fallback warning", + expect.anything(), + ); expect(printSummaryMock).toHaveBeenCalled(); expect(printActionSummaryMock).toHaveBeenCalled(); - expect(printCoverageMock).toHaveBeenCalledWith(["Parser note", "Coverage note"]); - expect(printSkippedDependenciesMock).toHaveBeenCalledWith(["dependencies:debug@^4.3.0"]); + expect(printCoverageMock).toHaveBeenCalledWith([ + "Parser note", + "Coverage note", + ]); + expect(printSkippedDependenciesMock).toHaveBeenCalledWith([ + "dependencies:debug@^4.3.0", + ]); expect(printTableMock).toHaveBeenCalled(); expect(printFinalStatusMock).toHaveBeenCalled(); expect(printCompactOutputMock).not.toHaveBeenCalled(); @@ -618,7 +740,9 @@ describe("CLI integration", () => { "/project/apiServer/package-lock.json", ]); // real handleMultiFolderScan always calls process.exit; mirror that in the mock - handleMultiFolderScanMock.mockImplementation(async () => { process.exit(0); }); + handleMultiFolderScanMock.mockImplementation(async () => { + process.exit(0); + }); const result = await runIndexModule(); @@ -642,7 +766,9 @@ describe("CLI integration", () => { it("does not route to multi-folder scan when only 1 nested lockfile found", async () => { hasRootLockfileMock.mockReturnValue(false); - findNestedLockfilesMock.mockReturnValue(["/project/sessionManager/package-lock.json"]); + findNestedLockfilesMock.mockReturnValue([ + "/project/sessionManager/package-lock.json", + ]); loadPackagesMock.mockReturnValue(createScanInput()); await runIndexModule(); @@ -663,21 +789,37 @@ describe("CLI integration", () => { }, projectArg: ".", }); - loadPackagesMock.mockReturnValue(createScanInput({ - packages: [{ name: "lodash", version: "4.17.21", ecosystem: "npm", paths: [["project", "lodash"]] }], - })); + loadPackagesMock.mockReturnValue( + createScanInput({ + packages: [ + { + name: "lodash", + version: "4.17.21", + ecosystem: "npm", + paths: [["project", "lodash"]], + }, + ], + }), + ); const result = await runIndexModule(); expect(result.exitCode).toBe(0); - expect(stripAnsi(result.stdout[0] ?? "")).toContain("Offline mode: enabled"); - expect(stripAnsi(result.stdout[1] ?? "")).toContain("Advisory source: local advisory database"); + expect(stripAnsi(result.stdout[0] ?? "")).toContain( + "Offline mode: enabled", + ); + expect(stripAnsi(result.stdout[1] ?? "")).toContain( + "Advisory source: local advisory database", + ); expect(stripAnsi(result.stdout[1] ?? "")).toContain("/tmp/advisories.db"); - expect(stripAnsi(result.stdout[2] ?? "")).toContain("Advisory DB freshness: synced"); + expect(stripAnsi(result.stdout[2] ?? "")).toContain( + "Advisory DB freshness: synced", + ); }); it("prints the advisories sync hint when the offline DB cannot be opened", async () => { - const createAdvisorySourceMock = (await import("../src/scanner.js")).createAdvisorySource as jest.Mock; + const createAdvisorySourceMock = (await import("../src/scanner.js")) + .createAdvisorySource as jest.Mock; createAdvisorySourceMock.mockImplementationOnce(() => { throw new Error("file does not exist"); }); @@ -697,12 +839,17 @@ describe("CLI integration", () => { const result = await runIndexModule(); expect(result.exitCode).toBe(1); - expect(stripAnsi(result.stderr.join("\n"))).toContain("Offline advisory database is not available: file does not exist"); - expect(stripAnsi(result.stderr.join("\n"))).toContain("To build it, run: cve-lite advisories sync"); + expect(stripAnsi(result.stderr.join("\n"))).toContain( + "Offline advisory database is not available: file does not exist", + ); + expect(stripAnsi(result.stderr.join("\n"))).toContain( + "To build it, run: cve-lite advisories sync", + ); }); it("prints the requested output path when a custom offline DB cannot be opened", async () => { - const createAdvisorySourceMock = (await import("../src/scanner.js")).createAdvisorySource as jest.Mock; + const createAdvisorySourceMock = (await import("../src/scanner.js")) + .createAdvisorySource as jest.Mock; createAdvisorySourceMock.mockImplementationOnce(() => { throw new Error("permission denied"); }); @@ -722,20 +869,30 @@ describe("CLI integration", () => { const result = await runIndexModule(); expect(result.exitCode).toBe(1); - expect(stripAnsi(result.stderr.join("\n"))).toContain("Offline advisory database is not available: permission denied"); - expect(stripAnsi(result.stderr.join("\n"))).toContain("To build it, run: cve-lite advisories sync"); - expect(stripAnsi(result.stderr.join("\n"))).toContain("cve-lite advisories sync --output /tmp/custom-advisories.db"); + expect(stripAnsi(result.stderr.join("\n"))).toContain( + "Offline advisory database is not available: permission denied", + ); + expect(stripAnsi(result.stderr.join("\n"))).toContain( + "To build it, run: cve-lite advisories sync", + ); + expect(stripAnsi(result.stderr.join("\n"))).toContain( + "cve-lite advisories sync --output /tmp/custom-advisories.db", + ); }); it("warns when the local advisory DB appears stale", async () => { - const createAdvisorySourceMock = (await import("../src/scanner.js")).createAdvisorySource as jest.Mock; + const createAdvisorySourceMock = (await import("../src/scanner.js")) + .createAdvisorySource as jest.Mock; createAdvisorySourceMock.mockReturnValueOnce({ advisorySource: { queryBatch: jest.fn(), getVuln: jest.fn() }, offline: true, sourceLabel: "local advisory database (/tmp/advisories.db)", advisoryDbMetadata: { - lastSyncAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), - sourceUrl: "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", + lastSyncAt: new Date( + Date.now() - 10 * 24 * 60 * 60 * 1000, + ).toISOString(), + sourceUrl: + "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", }, advisoryDbIsStale: true, cleanup: jest.fn(), @@ -754,9 +911,18 @@ describe("CLI integration", () => { }, projectArg: ".", }); - loadPackagesMock.mockReturnValue(createScanInput({ - packages: [{ name: "lodash", version: "4.17.21", ecosystem: "npm", paths: [["project", "lodash"]] }], - })); + loadPackagesMock.mockReturnValue( + createScanInput({ + packages: [ + { + name: "lodash", + version: "4.17.21", + ecosystem: "npm", + paths: [["project", "lodash"]], + }, + ], + }), + ); const result = await runIndexModule(); @@ -771,7 +937,8 @@ describe("CLI integration", () => { syncOsvAdvisoriesMock.mockResolvedValue({ advisoryCount: 2, dbPath: "/tmp/advisories.db", - sourceUrl: "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", + sourceUrl: + "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", }); parseArgsMock.mockReturnValue({ command: "advisories-sync", @@ -788,15 +955,33 @@ describe("CLI integration", () => { expect(result.exitCode).toBe(0); expect(syncOsvAdvisoriesMock).toHaveBeenCalledWith( - expect.objectContaining({ outputPath: "/tmp/advisories.db", onProgress: expect.any(Function) }), + expect.objectContaining({ + outputPath: "/tmp/advisories.db", + onProgress: expect.any(Function), + }), ); expect(loadPackagesMock).not.toHaveBeenCalled(); - expect(result.stdout.some(line => stripAnsi(line).includes("Advisory sync complete (2 records)"))).toBe(true); - expect(result.stdout.some(line => stripAnsi(line).includes("Advisory database: synced 2 records"))).toBe(true); + expect( + result.stdout.some((line) => + stripAnsi(line).includes("Advisory sync complete (2 records)"), + ), + ).toBe(true); + expect( + result.stdout.some((line) => + stripAnsi(line).includes("Advisory database: synced 2 records"), + ), + ).toBe(true); }); it("applies validated direct fixes and rescans in --fix mode", async () => { - const finding = createFinding({ pkg: { name: "multer", version: "1.4.5-lts.2", ecosystem: "npm", paths: [["project", "multer"]] } }); + const finding = createFinding({ + pkg: { + name: "multer", + version: "1.4.5-lts.2", + ecosystem: "npm", + paths: [["project", "multer"]], + }, + }); parseArgsMock.mockReturnValue({ command: "scan", options: { @@ -809,8 +994,20 @@ describe("CLI integration", () => { projectArg: ".", }); loadPackagesMock - .mockReturnValueOnce(createScanInput({ source: "package-lock", filePath: "/tmp/project/package-lock.json", packages: [finding.pkg] })) - .mockReturnValueOnce(createScanInput({ source: "package-lock", filePath: "/tmp/project/package-lock.json", packages: [finding.pkg] })); + .mockReturnValueOnce( + createScanInput({ + source: "package-lock", + filePath: "/tmp/project/package-lock.json", + packages: [finding.pkg], + }), + ) + .mockReturnValueOnce( + createScanInput({ + source: "package-lock", + filePath: "/tmp/project/package-lock.json", + packages: [finding.pkg], + }), + ); scanPackagesMock.mockResolvedValueOnce([finding]).mockResolvedValueOnce([]); buildSuggestedFixCommandPlanMock.mockReturnValueOnce({ packageManager: "npm", @@ -839,7 +1036,10 @@ describe("CLI integration", () => { expect(spawnMock).toHaveBeenCalledWith( "npm", ["install", "multer@2.1.1"], - expect.objectContaining({ cwd: expect.any(String), stdio: ["ignore", "pipe", "pipe"] }), + expect.objectContaining({ + cwd: expect.any(String), + stdio: ["ignore", "pipe", "pipe"], + }), ); expect(loadPackagesMock).toHaveBeenCalledTimes(2); expect(scanPackagesMock).toHaveBeenCalledTimes(2); @@ -854,7 +1054,9 @@ describe("CLI integration", () => { setupFixModeWithNpmDirectTarget(); spawnMock.mockImplementationOnce(() => { const child = createMockSpawnChild(); - process.nextTick(() => child.emit("error", new Error("spawn npm ENOENT"))); + process.nextTick(() => + child.emit("error", new Error("spawn npm ENOENT")), + ); return child; }); @@ -901,7 +1103,9 @@ describe("CLI integration", () => { const result = await runIndexModule(); expect(result.exitCode).toBe(1); - expect(result.stderr.join("\n")).toContain("--fix cannot be used with --json"); + expect(result.stderr.join("\n")).toContain( + "--fix cannot be used with --json", + ); }); it("fails fast when --create-pr is used without --fix", async () => { @@ -926,27 +1130,57 @@ describe("CLI integration", () => { it("throws when --no-cache is used with --offline", async () => { parseArgsMock.mockReturnValue({ command: "scan", - options: { failOn: "critical", batchSize: "100", noCache: true, offline: true }, + options: { + failOn: "critical", + batchSize: "100", + noCache: true, + offline: true, + }, projectArg: ".", }); - loadPackagesMock.mockResolvedValue({ scanInput: { packages: [], notes: [], warnings: [], skippedDependencies: [] }, projectPath: "." }); + loadPackagesMock.mockResolvedValue({ + scanInput: { + packages: [], + notes: [], + warnings: [], + skippedDependencies: [], + }, + projectPath: ".", + }); const result = await runIndexModule(); expect(result.exitCode).toBe(1); - expect(result.stderr.join("\n")).toContain("--no-cache cannot be used with --offline"); + expect(result.stderr.join("\n")).toContain( + "--no-cache cannot be used with --offline", + ); }); it("throws when --no-cache is used with --offline-db", async () => { parseArgsMock.mockReturnValue({ command: "scan", - options: { failOn: "critical", batchSize: "100", noCache: true, offlineDb: "/tmp/advisories.db" }, + options: { + failOn: "critical", + batchSize: "100", + noCache: true, + offlineDb: "/tmp/advisories.db", + }, projectArg: ".", }); - loadPackagesMock.mockResolvedValue({ scanInput: { packages: [], notes: [], warnings: [], skippedDependencies: [] }, projectPath: "." }); + loadPackagesMock.mockResolvedValue({ + scanInput: { + packages: [], + notes: [], + warnings: [], + skippedDependencies: [], + }, + projectPath: ".", + }); const result = await runIndexModule(); expect(result.exitCode).toBe(1); - expect(result.stderr.join("\n")).toContain("--no-cache cannot be used with --offline"); + expect(result.stderr.join("\n")).toContain( + "--no-cache cannot be used with --offline", + ); }); it("routes install-skill command, calls installSkill with cwd, and exits 0", async () => { @@ -961,7 +1195,12 @@ describe("CLI integration", () => { describe("--report flag", () => { it("calls writeHtmlReport and prints the report path", async () => { const packages = [ - { name: "lodash", version: "4.17.21", ecosystem: "npm", paths: [["project", "lodash"]] }, + { + name: "lodash", + version: "4.17.21", + ecosystem: "npm", + paths: [["project", "lodash"]], + }, ]; loadPackagesMock.mockReturnValue(createScanInput({ packages })); scanPackagesMock.mockResolvedValue([]); @@ -984,7 +1223,7 @@ describe("CLI integration", () => { expect.objectContaining({ outputDir: expect.stringContaining("my-report"), autoOpen: false, - }) + }), ); const output = result.stdout.join("\n"); expect(output).toContain("/tmp/cve-report/index.html"); @@ -1006,12 +1245,19 @@ describe("CLI integration", () => { const result = await runIndexModule(); - expect(result.stderr.join("\n")).toContain("--report cannot be used with --json"); + expect(result.stderr.join("\n")).toContain( + "--report cannot be used with --json", + ); }); it("uses ./cve-report as default output dir when --report is true (boolean)", async () => { const packages = [ - { name: "lodash", version: "4.17.21", ecosystem: "npm", paths: [["project", "lodash"]] }, + { + name: "lodash", + version: "4.17.21", + ecosystem: "npm", + paths: [["project", "lodash"]], + }, ]; loadPackagesMock.mockReturnValue(createScanInput({ packages })); scanPackagesMock.mockResolvedValue([]); @@ -1030,7 +1276,8 @@ describe("CLI integration", () => { await runIndexModule(); - const callArgs: Parameters[0] = writeHtmlReportMock.mock.calls[0][0]; + const callArgs: Parameters[0] = + writeHtmlReportMock.mock.calls[0][0]; expect(callArgs.outputDir).toContain("cve-report"); }); }); diff --git a/tests/create-pr.test.ts b/tests/create-pr.test.ts index 5b2e9fda..1242a715 100644 --- a/tests/create-pr.test.ts +++ b/tests/create-pr.test.ts @@ -36,21 +36,28 @@ function createFinding(overrides?: Partial): Finding { describe("create-pr helpers", () => { it("builds a dated branch name", () => { - expect(defaultFixBranchName(new Date("2026-05-30T12:00:00Z"))).toBe("cve-lite/fix-2026-05-30"); + expect(defaultFixBranchName(new Date("2026-05-30T12:00:00Z"))).toBe( + "cve-lite/fix-2026-05-30", + ); }); it("builds a pull request title for applied fixes", () => { expect(buildPullRequestTitle(1, ["lodash"])).toBe( "[CVE-Lite-CLI] fix: upgrade lodash (1 vulnerability resolved)", ); - expect(buildPullRequestTitle(4, ["lodash", "axios", "express", "minimist"])).toBe( + expect( + buildPullRequestTitle(4, ["lodash", "axios", "express", "minimist"]), + ).toBe( "[CVE-Lite-CLI] fix: upgrade lodash, axios +2 more (4 vulnerabilities resolved)", ); }); it("collects OSV and CVE identifiers for a package", () => { const findings = [createFinding()]; - expect(collectAdvisoryIdsForPackage(findings, "lodash")).toEqual(["CVE-2026-0001", "GHSA-abc"]); + expect(collectAdvisoryIdsForPackage(findings, "lodash")).toEqual([ + "CVE-2026-0001", + "GHSA-abc", + ]); }); it("builds a markdown body with fixes and scan counts", () => { @@ -63,7 +70,12 @@ describe("create-pr helpers", () => { applied: [{ package: "lodash", from: "4.17.20", to: "4.17.21" }], note: null, }, - findingsBeforeFix: [createFinding(), createFinding({ pkg: { name: "express", version: "4.0.0", ecosystem: "npm" } })], + findingsBeforeFix: [ + createFinding(), + createFinding({ + pkg: { name: "express", version: "4.0.0", ecosystem: "npm" }, + }), + ], findingsAfterFix: [createFinding()], }); @@ -77,32 +89,65 @@ describe("create-pr helpers", () => { }); it("checks fail-on threshold against initial findings", () => { - expect(findingsMeetFailOnThreshold([createFinding({ severity: "high" })], "critical")).toBe(false); - expect(findingsMeetFailOnThreshold([createFinding({ severity: "critical" })], "critical")).toBe(true); + expect( + findingsMeetFailOnThreshold( + [createFinding({ severity: "high" })], + "critical", + ), + ).toBe(false); + expect( + findingsMeetFailOnThreshold( + [createFinding({ severity: "critical" })], + "critical", + ), + ).toBe(true); }); it("adds numeric suffix when branch already exists", async () => { - const branch = await selectAvailableBranchName(process.cwd(), "cve-lite/fix-2026-06-02"); + const branch = await selectAvailableBranchName( + process.cwd(), + "cve-lite/fix-2026-06-02", + ); expect(branch).toMatch(/^cve-lite\/fix-2026-06-02(?:-\d+)?$/); }); it("stages only existing dependency files without failing on missing lockfiles", async () => { - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cve-lite-create-pr-")); + const tmpDir = fs.mkdtempSync( + path.join(os.tmpdir(), "cve-lite-create-pr-"), + ); try { execSync("git init", { cwd: tmpDir, stdio: "ignore" }); - execSync('git config user.email "test@example.com"', { cwd: tmpDir, stdio: "ignore" }); - execSync('git config user.name "Test User"', { cwd: tmpDir, stdio: "ignore" }); + execSync('git config user.email "test@example.com"', { + cwd: tmpDir, + stdio: "ignore", + }); + execSync('git config user.name "Test User"', { + cwd: tmpDir, + stdio: "ignore", + }); fs.writeFileSync(path.join(tmpDir, "package.json"), '{"name":"test"}\n'); - fs.writeFileSync(path.join(tmpDir, "package-lock.json"), '{"lockfileVersion":3}\n'); - execSync("git add package.json package-lock.json", { cwd: tmpDir, stdio: "ignore" }); + fs.writeFileSync( + path.join(tmpDir, "package-lock.json"), + '{"lockfileVersion":3}\n', + ); + execSync("git add package.json package-lock.json", { + cwd: tmpDir, + stdio: "ignore", + }); execSync('git commit -m "init"', { cwd: tmpDir, stdio: "ignore" }); - fs.writeFileSync(path.join(tmpDir, "package.json"), '{"name":"test","version":"1.0.1"}\n'); + fs.writeFileSync( + path.join(tmpDir, "package.json"), + '{"name":"test","version":"1.0.1"}\n', + ); await expect(stageDependencyFilesOnly(tmpDir)).resolves.toBeUndefined(); - const staged = execSync("git diff --cached --name-only", { cwd: tmpDir, encoding: "utf8" }); + const staged = execSync("git diff --cached --name-only", { + cwd: tmpDir, + encoding: "utf8", + }); expect(staged.trim().split("\n")).toEqual(["package.json"]); } finally { fs.rmSync(tmpDir, { recursive: true, force: true }); @@ -112,15 +157,26 @@ describe("create-pr helpers", () => { describe("create-pr CLI options", () => { it("parses --create-pr and --base", () => { - const result = parseArgs([".", "--fix", "--create-pr", "--base", "develop"]); + const result = parseArgs([ + ".", + "--fix", + "--create-pr", + "--base", + "develop", + ]); expect(result.options.fix).toBe(true); expect(result.options.createPr).toBe(true); expect(result.options.prBase).toBe("develop"); }); it("requires --fix for --create-pr", () => { - expect(() => validateOptions({ failOn: "critical", batchSize: "100", searchDepth: "4", createPr: true })).toThrow( - "--create-pr requires --fix", - ); + expect(() => + validateOptions({ + failOn: "critical", + batchSize: "100", + searchDepth: "4", + createPr: true, + }), + ).toThrow("--create-pr requires --fix"); }); }); diff --git a/tests/cyclonedx.test.ts b/tests/cyclonedx.test.ts index afe66a08..55c82ef0 100644 --- a/tests/cyclonedx.test.ts +++ b/tests/cyclonedx.test.ts @@ -5,7 +5,12 @@ function makePackage(name: string, version: string): PackageRef { return { name, version, ecosystem: "npm" }; } -function makeFinding(name: string, version: string, cveId: string, severity: "critical" | "high" | "medium" | "low" | "unknown" = "high"): Finding { +function makeFinding( + name: string, + version: string, + cveId: string, + severity: "critical" | "high" | "medium" | "low" | "unknown" = "high", +): Finding { return { pkg: makePackage(name, version), vulnerabilities: [{ id: cveId }], @@ -24,7 +29,9 @@ describe("buildPurl", () => { }); it("encodes scoped package @ prefix", () => { - expect(buildPurl("@babel/core", "7.0.0")).toBe("pkg:npm/%40babel/core@7.0.0"); + expect(buildPurl("@babel/core", "7.0.0")).toBe( + "pkg:npm/%40babel/core@7.0.0", + ); }); }); @@ -49,7 +56,7 @@ describe("buildCycloneDxBom", () => { const findings = [makeFinding("lodash", "4.17.21", "CVE-2021-1234")]; const bom = buildCycloneDxBom(allPackages, findings, null, "1.0.0"); expect(bom.components).toHaveLength(3); - const names = bom.components.map(c => c.name); + const names = bom.components.map((c) => c.name); expect(names).toContain("lodash"); expect(names).toContain("@babel/core"); expect(names).toContain("express"); @@ -57,7 +64,7 @@ describe("buildCycloneDxBom", () => { it("sets correct bom-ref and purl on components", () => { const bom = buildCycloneDxBom(allPackages, [], null, "1.0.0"); - const scoped = bom.components.find(c => c.name === "@babel/core")!; + const scoped = bom.components.find((c) => c.name === "@babel/core")!; expect(scoped["bom-ref"]).toBe("pkg:npm/%40babel/core@7.0.0"); expect(scoped.purl).toBe("pkg:npm/%40babel/core@7.0.0"); }); @@ -117,7 +124,12 @@ describe("buildCycloneDxBom", () => { }); it("populates metadata.component from projectMeta", () => { - const bom = buildCycloneDxBom(allPackages, [], { name: "my-app", version: "1.2.3" }, "1.0.0"); + const bom = buildCycloneDxBom( + allPackages, + [], + { name: "my-app", version: "1.2.3" }, + "1.0.0", + ); expect(bom.metadata.component?.name).toBe("my-app"); expect(bom.metadata.component?.version).toBe("1.2.3"); }); diff --git a/tests/fixture-scan.test.ts b/tests/fixture-scan.test.ts index b183f348..d96b5ade 100644 --- a/tests/fixture-scan.test.ts +++ b/tests/fixture-scan.test.ts @@ -2,7 +2,11 @@ import fs from "node:fs"; import path from "node:path"; import { loadPackages } from "../src/parsers/index.js"; import { buildSuggestedFixCommandPlan } from "../src/remediation/fix-commands.js"; -import { isPrivateRegistrySource, isGitSource, hasCommitShaPinning } from "../src/utils/advisory.js"; +import { + isPrivateRegistrySource, + isGitSource, + hasCommitShaPinning, +} from "../src/utils/advisory.js"; import type { Finding, PackageRef, ScanInput } from "../src/types.js"; const examplesDir = path.join(process.cwd(), "examples"); @@ -11,14 +15,25 @@ function loadFixture(name: string): ScanInput { return loadPackages(path.join(examplesDir, name), false, 4); } -function itWithFixture(name: string, testName: string, testFn: () => void): void { - const fixtureTest = fs.existsSync(path.join(examplesDir, name)) ? it : it.skip; +function itWithFixture( + name: string, + testName: string, + testFn: () => void, +): void { + const fixtureTest = fs.existsSync(path.join(examplesDir, name)) + ? it + : it.skip; fixtureTest(testName, testFn); } -function requirePackage(scanInput: ScanInput, name: string, version?: string): PackageRef { +function requirePackage( + scanInput: ScanInput, + name: string, + version?: string, +): PackageRef { const pkg = scanInput.packages.find( - item => item.name === name && (version === undefined || item.version === version), + (item) => + item.name === name && (version === undefined || item.version === version), ); if (!pkg) { const versionSuffix = version ? `@${version}` : ""; @@ -57,7 +72,8 @@ describe("fixture remediation scans", () => { currentVersion: "3.0.6", targetChildVersion: "3.0.7", viaPath: ["project", "@aws-amplify/core", "js-cookie"], - reason: "js-cookie can be refreshed within @aws-amplify/core's declared range.", + reason: + "js-cookie can be refreshed within @aws-amplify/core's declared range.", }, }); @@ -74,7 +90,10 @@ describe("fixture remediation scans", () => { ]); expect(plan?.targets).not.toEqual( expect.arrayContaining([ - expect.objectContaining({ package: "@aws-amplify/core", kind: "parent-upgrade" }), + expect.objectContaining({ + package: "@aws-amplify/core", + kind: "parent-upgrade", + }), ]), ); }); @@ -94,7 +113,8 @@ describe("fixture remediation scans", () => { currentVersion: "6.16.1", targetChildVersion: "3.0.7", viaPath: ["project", "@aws-amplify/core", "js-cookie"], - reason: "js-cookie can be refreshed within @aws-amplify/core's declared range.", + reason: + "js-cookie can be refreshed within @aws-amplify/core's declared range.", }, }); @@ -111,7 +131,10 @@ describe("fixture remediation scans", () => { ]); expect(plan?.targets).not.toEqual( expect.arrayContaining([ - expect.objectContaining({ package: "@aws-amplify/core", kind: "parent-upgrade" }), + expect.objectContaining({ + package: "@aws-amplify/core", + kind: "parent-upgrade", + }), ]), ); }, @@ -194,9 +217,11 @@ describe("fixture remediation scans", () => { const lodash3 = requirePackage(scanInput, "lodash", "3.10.1"); const lodash4 = requirePackage(scanInput, "lodash", "4.17.20"); - expect(scanInput.packages.filter(item => item.name === "lodash")).toHaveLength(2); - expect(lodash3.paths?.every(path => path.length > 2)).toBe(true); - expect(lodash4.paths?.some(path => path.length <= 2)).toBe(true); + expect( + scanInput.packages.filter((item) => item.name === "lodash"), + ).toHaveLength(2); + expect(lodash3.paths?.every((path) => path.length > 2)).toBe(true); + expect(lodash4.paths?.some((path) => path.length <= 2)).toBe(true); const directFinding = findingFor(scanInput, "lodash", { relationship: "direct", @@ -213,59 +238,79 @@ describe("fixture remediation scans", () => { }); transitiveFinding.pkg = lodash3; - const directPlan = buildSuggestedFixCommandPlan([directFinding], scanInput); - const transitivePlan = buildSuggestedFixCommandPlan([transitiveFinding], scanInput); + const directPlan = buildSuggestedFixCommandPlan( + [directFinding], + scanInput, + ); + const transitivePlan = buildSuggestedFixCommandPlan( + [transitiveFinding], + scanInput, + ); expect(directPlan?.command).toBe("npm install lodash@4.18.0"); - expect(directPlan?.targets.find(t => t.kind === "direct")?.package).toBe("lodash"); - expect(transitivePlan?.targets.find(t => t.kind === "direct")).toBeUndefined(); + expect( + directPlan?.targets.find((t) => t.kind === "direct")?.package, + ).toBe("lodash"); + expect( + transitivePlan?.targets.find((t) => t.kind === "direct"), + ).toBeUndefined(); }, ); it("mal-private-registry fixture - node-ipc resolvedUrl is from a private registry", () => { const scanInput = loadFixture("mal-private-registry"); - const nodeIpc = scanInput.packages.find(p => p.name === "node-ipc"); + const nodeIpc = scanInput.packages.find((p) => p.name === "node-ipc"); expect(nodeIpc).toBeDefined(); - expect(nodeIpc?.resolvedUrl).toBe("https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz"); + expect(nodeIpc?.resolvedUrl).toBe( + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + ); expect(isPrivateRegistrySource(nodeIpc!)).toBe(true); }); it("pnpm-mal-private-registry fixture - node-ipc resolvedUrl is from a private registry", () => { const scanInput = loadFixture("pnpm-mal-private-registry"); - const nodeIpc = scanInput.packages.find(p => p.name === "node-ipc"); + const nodeIpc = scanInput.packages.find((p) => p.name === "node-ipc"); expect(nodeIpc).toBeDefined(); - expect(nodeIpc?.resolvedUrl).toBe("https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz"); + expect(nodeIpc?.resolvedUrl).toBe( + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + ); expect(isPrivateRegistrySource(nodeIpc!)).toBe(true); }); it("pnpm-legacy-mal-private-registry fixture - node-ipc resolvedUrl is from a private registry", () => { const scanInput = loadFixture("pnpm-legacy-mal-private-registry"); - const nodeIpc = scanInput.packages.find(p => p.name === "node-ipc"); + const nodeIpc = scanInput.packages.find((p) => p.name === "node-ipc"); expect(nodeIpc).toBeDefined(); - expect(nodeIpc?.resolvedUrl).toBe("https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz"); + expect(nodeIpc?.resolvedUrl).toBe( + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + ); expect(isPrivateRegistrySource(nodeIpc!)).toBe(true); }); it("yarn-classic-mal-private-registry fixture - node-ipc resolvedUrl is from a private registry", () => { const scanInput = loadFixture("yarn-classic-mal-private-registry"); - const nodeIpc = scanInput.packages.find(p => p.name === "node-ipc"); + const nodeIpc = scanInput.packages.find((p) => p.name === "node-ipc"); expect(nodeIpc).toBeDefined(); - expect(nodeIpc?.resolvedUrl).toBe("https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz"); + expect(nodeIpc?.resolvedUrl).toBe( + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + ); expect(isPrivateRegistrySource(nodeIpc!)).toBe(true); }); it("bun-mal-private-registry fixture - node-ipc resolvedUrl is from a private registry", () => { const scanInput = loadFixture("bun-mal-private-registry"); - const nodeIpc = scanInput.packages.find(p => p.name === "node-ipc"); + const nodeIpc = scanInput.packages.find((p) => p.name === "node-ipc"); expect(nodeIpc).toBeDefined(); - expect(nodeIpc?.resolvedUrl).toBe("https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz"); + expect(nodeIpc?.resolvedUrl).toBe( + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + ); expect(isPrivateRegistrySource(nodeIpc!)).toBe(true); }); it("git-source-mal fixture - node-ipc is detected as git source with SHA pinning", () => { const scanInput = loadFixture("git-source-mal"); - const nodeIpc = scanInput.packages.find(p => p.name === "node-ipc"); + const nodeIpc = scanInput.packages.find((p) => p.name === "node-ipc"); expect(nodeIpc).toBeDefined(); expect(isGitSource(nodeIpc!)).toBe(true); expect(hasCommitShaPinning(nodeIpc!)).toBe(true); diff --git a/tests/help.test.ts b/tests/help.test.ts index fe800c82..300575b4 100644 --- a/tests/help.test.ts +++ b/tests/help.test.ts @@ -4,15 +4,17 @@ import { stripAnsi } from "../src/utils/chalk.js"; function captureLogs(run: () => void): string[] { const logs: string[] = []; - const spy = jest.spyOn(console, "log").mockImplementation((...args: unknown[]) => { - logs.push(args.map(arg => String(arg)).join(" ")); - }); + const spy = jest + .spyOn(console, "log") + .mockImplementation((...args: unknown[]) => { + logs.push(args.map((arg) => String(arg)).join(" ")); + }); try { run(); } finally { spy.mockRestore(); } - return logs.map(line => stripAnsi(line)); + return logs.map((line) => stripAnsi(line)); } describe("printBanner", () => { @@ -30,7 +32,7 @@ describe("printBanner", () => { const logs = captureLogs(() => { printBanner(); }); - expect(logs.some(line => line.includes("CVE Lite CLI"))).toBe(true); + expect(logs.some((line) => line.includes("CVE Lite CLI"))).toBe(true); } finally { delete process.env["NO_UPDATE_NOTIFIER"]; } @@ -47,14 +49,30 @@ describe("printHelp", () => { const output = logs.join("\n"); expect(output).toContain("Examples:"); - expect(output).toContain("cve-lite . Scan the current directory"); - expect(output).toContain("cve-lite . --verbose Full output with fix plan and findings table"); - expect(output).toContain("cve-lite . --fail-on high Exit 1 if any high or critical findings are found"); - expect(output).toContain("cve-lite . --json Write findings to a timestamped JSON file"); - expect(output).toContain("cve-lite . --report Generate an interactive HTML report"); - expect(output).toContain("cve-lite . --fix Apply validated direct dependency fixes and rescan"); - expect(output).toContain("cve-lite . --offline Scan using the local advisory database (no network)"); - expect(output).toContain("cve-lite advisories sync Sync the local advisory database"); + expect(output).toContain( + "cve-lite . Scan the current directory", + ); + expect(output).toContain( + "cve-lite . --verbose Full output with fix plan and findings table", + ); + expect(output).toContain( + "cve-lite . --fail-on high Exit 1 if any high or critical findings are found", + ); + expect(output).toContain( + "cve-lite . --json Write findings to a timestamped JSON file", + ); + expect(output).toContain( + "cve-lite . --report Generate an interactive HTML report", + ); + expect(output).toContain( + "cve-lite . --fix Apply validated direct dependency fixes and rescan", + ); + expect(output).toContain( + "cve-lite . --offline Scan using the local advisory database (no network)", + ); + expect(output).toContain( + "cve-lite advisories sync Sync the local advisory database", + ); } finally { delete process.env["NO_UPDATE_NOTIFIER"]; } diff --git a/tests/helpers.test.ts b/tests/helpers.test.ts index 3f8ed223..a21ce77f 100644 --- a/tests/helpers.test.ts +++ b/tests/helpers.test.ts @@ -2,7 +2,11 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { parseArgs } from "../src/cli/args.js"; -import { maxSeverity, inferSeverity, normalizeSeverity } from "../src/osv/severity.js"; +import { + maxSeverity, + inferSeverity, + normalizeSeverity, +} from "../src/osv/severity.js"; import { chooseBestLockfile, findFiles, @@ -89,7 +93,12 @@ describe("parseArgs", () => { }); it("parses the advisories sync command and its output path option", () => { - const result = parseArgs(["advisories", "sync", "--output", "./tmp/advisories.db"]); + const result = parseArgs([ + "advisories", + "sync", + "--output", + "./tmp/advisories.db", + ]); expect(result).toEqual({ command: "advisories-sync", @@ -129,8 +138,12 @@ describe("parseArgs", () => { it("throws on unknown options and unexpected extra arguments", () => { expect(() => parseArgs(["--wat"])).toThrow("Unknown option: --wat"); - expect(() => parseArgs(["project-a", "project-b"])).toThrow("Unexpected argument: project-b"); - expect(() => parseArgs(["advisories", "sync", "extra"])).toThrow("Unexpected argument: extra"); + expect(() => parseArgs(["project-a", "project-b"])).toThrow( + "Unexpected argument: project-b", + ); + expect(() => parseArgs(["advisories", "sync", "extra"])).toThrow( + "Unexpected argument: extra", + ); }); it("parses install-skill command", () => { @@ -145,11 +158,15 @@ describe("parseArgs", () => { }); it("throws on unknown option for install-skill", () => { - expect(() => parseArgs(["install-skill", "--unknown"])).toThrow("Unknown option: --unknown"); + expect(() => parseArgs(["install-skill", "--unknown"])).toThrow( + "Unknown option: --unknown", + ); }); it("throws on unexpected argument for install-skill", () => { - expect(() => parseArgs(["install-skill", "extra"])).toThrow("Unexpected argument: extra"); + expect(() => parseArgs(["install-skill", "extra"])).toThrow( + "Unexpected argument: extra", + ); }); it("sets cdx option when --cdx is passed", () => { @@ -158,7 +175,9 @@ describe("parseArgs", () => { }); it("throws when --cdx and --report are combined", () => { - expect(() => parseArgs(["--cdx", "--report"])).toThrow("cannot combine --cdx and --report"); + expect(() => parseArgs(["--cdx", "--report"])).toThrow( + "cannot combine --cdx and --report", + ); }); it("allows --cdx combined with --json and --sarif", () => { @@ -169,7 +188,12 @@ describe("parseArgs", () => { }); it("allows --sarif combined with --report for dual CI output", () => { - const result = parseArgs(["--sarif", "--report", "./cve-report", "--no-open"]); + const result = parseArgs([ + "--sarif", + "--report", + "./cve-report", + "--no-open", + ]); expect(result.options.sarif).toBe(true); expect(result.options.report).toBe("./cve-report"); expect(result.options.noOpen).toBe(true); @@ -183,19 +207,38 @@ describe("parseArgs", () => { describe("severity helpers", () => { it("infers severity from score ranges and database fallback", () => { - expect(inferSeverity({ id: "1", severity: [{ score: "9.8" }] })).toBe("critical"); - expect(inferSeverity({ id: "2", severity: [{ score: "7.5" }] })).toBe("high"); - expect(inferSeverity({ id: "3", severity: [{ score: "5.6" }] })).toBe("medium"); - expect(inferSeverity({ id: "4", severity: [{ score: "2.1" }] })).toBe("low"); - expect(inferSeverity({ id: "5", severity: [{ score: "0.0" }] })).toBe("none"); - expect(inferSeverity({ id: "6", database_specific: { severity: "HIGH" } })).toBe("high"); + expect(inferSeverity({ id: "1", severity: [{ score: "9.8" }] })).toBe( + "critical", + ); + expect(inferSeverity({ id: "2", severity: [{ score: "7.5" }] })).toBe( + "high", + ); + expect(inferSeverity({ id: "3", severity: [{ score: "5.6" }] })).toBe( + "medium", + ); + expect(inferSeverity({ id: "4", severity: [{ score: "2.1" }] })).toBe( + "low", + ); + expect(inferSeverity({ id: "5", severity: [{ score: "0.0" }] })).toBe( + "none", + ); + expect( + inferSeverity({ id: "6", database_specific: { severity: "HIGH" } }), + ).toBe("high"); expect(inferSeverity({ id: "7" })).toBe("unknown"); }); it("maps OSV MODERATE label to medium", () => { // OSV uses "MODERATE" where our severity label is "medium". - expect(inferSeverity({ id: "got", database_specific: { severity: "MODERATE" } })).toBe("medium"); - expect(inferSeverity({ id: "got-lower", database_specific: { severity: "moderate" } })).toBe("medium"); + expect( + inferSeverity({ id: "got", database_specific: { severity: "MODERATE" } }), + ).toBe("medium"); + expect( + inferSeverity({ + id: "got-lower", + database_specific: { severity: "moderate" }, + }), + ).toBe("medium"); // Combined: CVSS vector falls through to MODERATE db label → medium expect( inferSeverity({ @@ -257,11 +300,11 @@ describe("version helpers", () => { }); it("detects major version bumps", () => { - expect(isMajorVersionBump("8.5.1", "9.0.0")).toBe(true); // 8 → 9 + expect(isMajorVersionBump("8.5.1", "9.0.0")).toBe(true); // 8 → 9 expect(isMajorVersionBump("3.6.2", "4.17.21")).toBe(true); // 3 → 4 - expect(isMajorVersionBump("5.8.4", "5.8.5")).toBe(false); // patch only - expect(isMajorVersionBump("1.2.3", "1.3.0")).toBe(false); // minor only - expect(isMajorVersionBump("1.0.0", "1.0.0")).toBe(false); // same + expect(isMajorVersionBump("5.8.4", "5.8.5")).toBe(false); // patch only + expect(isMajorVersionBump("1.2.3", "1.3.0")).toBe(false); // minor only + expect(isMajorVersionBump("1.0.0", "1.0.0")).toBe(false); // same expect(isMajorVersionBump("not-a-ver", "9.0.0")).toBe(false); // unparseable from expect(isMajorVersionBump("8.0.0", "not-a-ver")).toBe(false); // unparseable to }); @@ -317,7 +360,9 @@ describe("file helpers", () => { it("returns relative paths when possible and falls back to the file name", () => { const rootDir = "/tmp/project"; - expect(relativeOrName(rootDir, "/tmp/project/src/index.ts")).toBe(path.join("src", "index.ts")); + expect(relativeOrName(rootDir, "/tmp/project/src/index.ts")).toBe( + path.join("src", "index.ts"), + ); expect(relativeOrName(rootDir, rootDir)).toBe("project"); }); @@ -380,7 +425,7 @@ describe("runWithConcurrency", () => { it("returns results in input order regardless of completion order", async () => { const order: number[] = []; const results = await runWithConcurrency([30, 10, 20], 3, async (ms) => { - await new Promise(r => setTimeout(r, ms)); + await new Promise((r) => setTimeout(r, ms)); order.push(ms); return ms * 2; }); @@ -394,7 +439,7 @@ describe("runWithConcurrency", () => { await runWithConcurrency([1, 2, 3, 4, 5], 2, async () => { concurrent++; maxConcurrent = Math.max(maxConcurrent, concurrent); - await new Promise(r => setTimeout(r, 10)); + await new Promise((r) => setTimeout(r, 10)); concurrent--; }); expect(maxConcurrent).toBe(2); @@ -410,7 +455,7 @@ describe("runWithConcurrency", () => { runWithConcurrency([1, 2, 3], 3, async (n) => { if (n === 2) throw new Error("boom"); return n; - }) + }), ).rejects.toThrow("boom"); }); }); diff --git a/tests/html-reporter.test.ts b/tests/html-reporter.test.ts index 398efaf4..1430d715 100644 --- a/tests/html-reporter.test.ts +++ b/tests/html-reporter.test.ts @@ -2,7 +2,11 @@ import { jest } from "@jest/globals"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { buildReportData, renderHtmlReport, writeHtmlReport } from "../src/output/html-reporter.js"; +import { + buildReportData, + renderHtmlReport, + writeHtmlReport, +} from "../src/output/html-reporter.js"; import type { Finding, OsvVuln } from "../src/types.js"; import type { SuggestedFixCommandPlan } from "../src/remediation/fix-commands.js"; @@ -73,7 +77,10 @@ describe("buildReportData", () => { targets: [], skipped: [], }; - const data = buildReportData({ ...BASE_PARAMS, suggestedFixCommands: plan }); + const data = buildReportData({ + ...BASE_PARAMS, + suggestedFixCommands: plan, + }); expect(data.suggestedFixCommands).toBe(plan); }); @@ -103,8 +110,13 @@ describe("buildReportData", () => { skipped: [], }; - const data = buildReportData({ ...BASE_PARAMS, suggestedFixCommands: plan }); - expect(data.findings[0].runnableFixCommand).toBe("npm install lodash@4.17.21"); + const data = buildReportData({ + ...BASE_PARAMS, + suggestedFixCommands: plan, + }); + expect(data.findings[0].runnableFixCommand).toBe( + "npm install lodash@4.17.21", + ); }); it("emits a null runnableFixCommand for findings with no actionable plan target", () => { @@ -156,10 +168,10 @@ describe("renderHtmlReport", () => { const html = renderHtmlReport(data); const recommendedActionIdx = html.indexOf("

    Recommended action

    "); const riskSummaryIdx = html.indexOf( - "

    Risk summary

    ", + '

    Risk summary

    ', ); const nextActionIdx = html.indexOf( - "

    Next action

    ", + '

    Next action

    ', ); expect(recommendedActionIdx).toBeGreaterThan(-1); @@ -250,15 +262,24 @@ describe("renderHtmlReport", () => { }); it("links CVE IDs to osv.dev", () => { - expect(renderHtmlReport(data)).toContain("osv.dev/vulnerability/CVE-2021-23337"); + expect(renderHtmlReport(data)).toContain( + "osv.dev/vulnerability/CVE-2021-23337", + ); }); it("links GHSA IDs to github.com/advisories", () => { const ghsaFinding = makeFinding({ cveAliases: ["GHSA-abcd-1234-efgh"], - vulnerabilities: [makeVuln({ id: "GHSA-abcd-1234-efgh", aliases: ["GHSA-abcd-1234-efgh"] })], + vulnerabilities: [ + makeVuln({ + id: "GHSA-abcd-1234-efgh", + aliases: ["GHSA-abcd-1234-efgh"], + }), + ], }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [ghsaFinding] })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [ghsaFinding] }), + ); expect(html).toContain("github.com/advisories/GHSA-abcd-1234-efgh"); }); @@ -283,7 +304,9 @@ describe("renderHtmlReport", () => { skipped: [], }; - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, suggestedFixCommands: plan })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, suggestedFixCommands: plan }), + ); expect(html).toContain("npm install lodash@4.17.21"); expect(html).toContain('data-cmd="npm install lodash@4.17.21"'); @@ -323,9 +346,13 @@ describe("renderHtmlReport", () => { skipped: [], }; - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, suggestedFixCommands: plan })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, suggestedFixCommands: plan }), + ); - expect(html).toContain("Path-specific remediation. Run this command, then rescan; 1 other known path may still need separate parent upgrades."); + expect(html).toContain( + "Path-specific remediation. Run this command, then rescan; 1 other known path may still need separate parent upgrades.", + ); }); it("renders the descriptive recommendation without a Copy button when no runnable command exists", () => { @@ -351,19 +378,28 @@ describe("renderHtmlReport", () => { // The expanded action panel for this finding should be the explanatory note, // not a fix-cmd-inline command box with a Copy button. - expect(html).toContain('

    No parent dependency was identified for lodash'); + expect(html).toContain( + '

    No parent dependency was identified for lodash', + ); const recommendedActionIdx = html.indexOf("

    Recommended action

    "); const nextHeadingIdx = html.indexOf("
    ", recommendedActionIdx); - const recommendedActionBlock = html.slice(recommendedActionIdx, nextHeadingIdx); + const recommendedActionBlock = html.slice( + recommendedActionIdx, + nextHeadingIdx, + ); expect(recommendedActionBlock).not.toContain("copy-btn"); expect(recommendedActionBlock).not.toContain("fix-cmd-inline"); }); it("escapes sequences in embedded JSON", () => { const xssFinding = makeFinding({ - vulnerabilities: [makeVuln({ summary: '' })], + vulnerabilities: [ + makeVuln({ summary: "" }), + ], }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [xssFinding] })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [xssFinding] }), + ); const scriptIdx = html.indexOf("const reportData ="); const endOfJson = html.indexOf(";", scriptIdx); const jsonBlob = html.slice(scriptIdx, endOfJson); @@ -388,7 +424,11 @@ describe("renderHtmlReport", () => { }); const html = renderHtmlReport( - buildReportData({ ...BASE_PARAMS, findings: [finding], suggestedFixCommands: null }), + buildReportData({ + ...BASE_PARAMS, + findings: [finding], + suggestedFixCommands: null, + }), ); expect(html).toContain("

    Context

    "); @@ -406,7 +446,11 @@ describe("renderHtmlReport", () => { }); const html = renderHtmlReport( - buildReportData({ ...BASE_PARAMS, findings: [finding], suggestedFixCommands: null }), + buildReportData({ + ...BASE_PARAMS, + findings: [finding], + suggestedFixCommands: null, + }), ); expect(html).toContain("

    Context

    "); @@ -422,7 +466,11 @@ describe("renderHtmlReport", () => { }); const html = renderHtmlReport( - buildReportData({ ...BASE_PARAMS, findings: [finding], suggestedFixCommands: null }), + buildReportData({ + ...BASE_PARAMS, + findings: [finding], + suggestedFixCommands: null, + }), ); expect(html).toContain("

    Context

    "); @@ -434,9 +482,13 @@ describe("renderHtmlReport", () => { it("shows ⚠ No fix in the fix column when no fixed version is available", () => { const finding = makeFinding({ firstFixedVersion: null }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [finding] })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [finding] }), + ); expect(html).toContain("⚠ No fix"); - expect(html).toContain('title="No known fix — consider replacing this package"'); + expect(html).toContain( + 'title="No known fix — consider replacing this package"', + ); }); it("shows malicious tooltip when finding has MAL-* advisory", () => { @@ -444,8 +496,12 @@ describe("renderHtmlReport", () => { firstFixedVersion: null, vulnerabilities: [makeVuln({ id: "MAL-2025-21003" })], }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [finding] })); - expect(html).toContain('title="Malicious code advisory — remove this package"'); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [finding] }), + ); + expect(html).toContain( + 'title="Malicious code advisory — remove this package"', + ); expect(html).toContain("⚠ Malicious"); }); @@ -455,7 +511,9 @@ describe("renderHtmlReport", () => { vulnerabilities: [makeVuln({ id: "MAL-2025-99999" })], maliciousUnverifiable: true, }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [finding] })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [finding] }), + ); expect(html).toContain("Unverifiable (private source)"); expect(html).not.toContain("⚠ Malicious"); }); @@ -463,12 +521,22 @@ describe("renderHtmlReport", () => { it("renders git source SHA-pinned badge for maliciousGitSource findings", () => { const finding = makeFinding({ firstFixedVersion: null, - pkg: { name: "node-ipc", version: "9.2.3", ecosystem: "npm", resolvedUrl: "https://codeload.github.com/org/repo/tar.gz/9af9b3c49515b85598cd88de3e8cc20c7a98efbb" }, - vulnerabilities: [makeVuln({ id: "MAL-2022-1000", summary: "Malicious" })], + pkg: { + name: "node-ipc", + version: "9.2.3", + ecosystem: "npm", + resolvedUrl: + "https://codeload.github.com/org/repo/tar.gz/9af9b3c49515b85598cd88de3e8cc20c7a98efbb", + }, + vulnerabilities: [ + makeVuln({ id: "MAL-2022-1000", summary: "Malicious" }), + ], maliciousGitSource: true, maliciousGitSourcePinned: true, }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [finding] })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [finding] }), + ); expect(html).toContain("Git source (SHA-pinned)"); expect(html).not.toContain("⚠ Malicious"); expect(html).not.toContain("Unverifiable (private source)"); @@ -477,12 +545,21 @@ describe("renderHtmlReport", () => { it("renders git source floating ref badge for unpinned git source findings", () => { const finding = makeFinding({ firstFixedVersion: null, - pkg: { name: "node-ipc", version: "9.2.3", ecosystem: "npm", resolvedUrl: "https://github.com/org/repo/archive/main.tar.gz" }, - vulnerabilities: [makeVuln({ id: "MAL-2022-1000", summary: "Malicious" })], + pkg: { + name: "node-ipc", + version: "9.2.3", + ecosystem: "npm", + resolvedUrl: "https://github.com/org/repo/archive/main.tar.gz", + }, + vulnerabilities: [ + makeVuln({ id: "MAL-2022-1000", summary: "Malicious" }), + ], maliciousGitSource: true, maliciousGitSourcePinned: false, }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [finding] })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [finding] }), + ); expect(html).toContain("Git source (floating ref)"); expect(html).not.toContain("⚠ Malicious"); expect(html).not.toContain("Unverifiable (private source)"); @@ -490,13 +567,19 @@ describe("renderHtmlReport", () => { it("shows generic no-fix tooltip when finding has non-MAL advisory and no fix version", () => { const finding = makeFinding({ firstFixedVersion: null }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [finding] })); - expect(html).toContain('title="No known fix — consider replacing this package"'); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [finding] }), + ); + expect(html).toContain( + 'title="No known fix — consider replacing this package"', + ); }); it("does not show ⚠ No fix when a fixed version is available", () => { const finding = makeFinding({ firstFixedVersion: "4.17.21" }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [finding] })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [finding] }), + ); expect(html).toContain("4.17.21"); expect(html).not.toContain("⚠ No fix"); }); @@ -504,20 +587,34 @@ describe("renderHtmlReport", () => { describe("dev dependency badge", () => { it("renders 'direct · dev' badge with dev CSS class for devDependency findings", () => { const finding = makeFinding({ - pkg: { name: "lodash", version: "4.17.20", ecosystem: "npm", dev: true }, + pkg: { + name: "lodash", + version: "4.17.20", + ecosystem: "npm", + dev: true, + }, relationship: "direct", }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [finding] })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [finding] }), + ); expect(html).toContain('class="rel-badge dev"'); expect(html).toContain("direct · dev"); }); it("renders normal 'direct' badge without dev class for prod findings", () => { const finding = makeFinding({ - pkg: { name: "lodash", version: "4.17.20", ecosystem: "npm", dev: false }, + pkg: { + name: "lodash", + version: "4.17.20", + ecosystem: "npm", + dev: false, + }, relationship: "direct", }); - const html = renderHtmlReport(buildReportData({ ...BASE_PARAMS, findings: [finding] })); + const html = renderHtmlReport( + buildReportData({ ...BASE_PARAMS, findings: [finding] }), + ); expect(html).toContain('class="rel-badge direct"'); expect(html).not.toContain("· dev"); }); @@ -585,7 +682,9 @@ describe("writeHtmlReport", () => { const outputDir = path.join(tmpDir, "report"); const data = buildReportData(BASE_PARAMS); await writeHtmlReport({ outputDir, data, autoOpen: false }); - const json = JSON.parse(fs.readFileSync(path.join(outputDir, "report.json"), "utf8")); + const json = JSON.parse( + fs.readFileSync(path.join(outputDir, "report.json"), "utf8"), + ); expect(json.cliVersion).toBe("1.8.0"); expect(json.findings).toHaveLength(1); }); @@ -603,7 +702,9 @@ describe("writeHtmlReport", () => { const data2 = buildReportData({ ...BASE_PARAMS, cliVersion: "2.0.0" }); await writeHtmlReport({ outputDir, data: data1, autoOpen: false }); await writeHtmlReport({ outputDir, data: data2, autoOpen: false }); - const json = JSON.parse(fs.readFileSync(path.join(outputDir, "report.json"), "utf8")); + const json = JSON.parse( + fs.readFileSync(path.join(outputDir, "report.json"), "utf8"), + ); expect(json.cliVersion).toBe("2.0.0"); }); @@ -614,8 +715,9 @@ describe("writeHtmlReport", () => { }); try { const data = buildReportData(BASE_PARAMS); - await expect(writeHtmlReport({ outputDir: brokenDir, data, autoOpen: false })) - .rejects.toThrow(/Failed to create report directory/); + await expect( + writeHtmlReport({ outputDir: brokenDir, data, autoOpen: false }), + ).rejects.toThrow(/Failed to create report directory/); } finally { writeSpy.mockRestore(); } @@ -628,8 +730,9 @@ describe("writeHtmlReport", () => { }); try { const data = buildReportData(BASE_PARAMS); - await expect(writeHtmlReport({ outputDir, data, autoOpen: false })) - .rejects.toThrow(/Failed to write HTML report/); + await expect( + writeHtmlReport({ outputDir, data, autoOpen: false }), + ).rejects.toThrow(/Failed to write HTML report/); expect(fs.existsSync(outputDir)).toBe(false); } finally { writeSpy.mockRestore(); @@ -641,17 +744,21 @@ describe("writeHtmlReport", () => { fs.mkdirSync(outputDir, { recursive: true }); const marker = path.join(outputDir, "existing-file.txt"); fs.writeFileSync(marker, "do not delete me"); - const writeSpy = jest.spyOn(fs, "writeFileSync").mockImplementation((filePath: fs.PathOrFileDescriptor) => { - const base = typeof filePath === "string" ? path.basename(filePath) : ""; - if (base === "index.html" || base === "report.json") { - throw new Error("ENOSPC: no space left on device"); - } - return undefined; - }); + const writeSpy = jest + .spyOn(fs, "writeFileSync") + .mockImplementation((filePath: fs.PathOrFileDescriptor) => { + const base = + typeof filePath === "string" ? path.basename(filePath) : ""; + if (base === "index.html" || base === "report.json") { + throw new Error("ENOSPC: no space left on device"); + } + return undefined; + }); try { const data = buildReportData(BASE_PARAMS); - await expect(writeHtmlReport({ outputDir, data, autoOpen: false })) - .rejects.toThrow(/Failed to write HTML report/); + await expect( + writeHtmlReport({ outputDir, data, autoOpen: false }), + ).rejects.toThrow(/Failed to write HTML report/); expect(fs.existsSync(outputDir)).toBe(true); expect(fs.existsSync(marker)).toBe(true); } finally { diff --git a/tests/install-skill.test.ts b/tests/install-skill.test.ts index dd082974..03c0aac0 100644 --- a/tests/install-skill.test.ts +++ b/tests/install-skill.test.ts @@ -29,7 +29,9 @@ describe("SKILL_CONTENT", () => { }); it("uses ### not ## headings (## would break section-replace logic in install.ts)", () => { - const doubleHashLines = SKILL_CONTENT.split("\n").filter(l => /^## /.test(l)); + const doubleHashLines = SKILL_CONTENT.split("\n").filter((l) => + /^## /.test(l), + ); expect(doubleHashLines).toHaveLength(0); }); }); @@ -50,7 +52,7 @@ describe("installSkill", () => { installSkill(tmpDir); const content = fs.readFileSync( path.join(tmpDir, ".claude", "commands", "cve-lite.md"), - "utf-8" + "utf-8", ); expect(content).toContain("Run this skill with /cve-lite"); expect(content).toContain(SKILL_CONTENT); @@ -60,9 +62,11 @@ describe("installSkill", () => { installSkill(tmpDir); const content = fs.readFileSync( path.join(tmpDir, ".cursor", "rules", "cve-lite.mdc"), - "utf-8" + "utf-8", + ); + expect(content).toContain( + "description: CVE Lite CLI vulnerability analysis", ); - expect(content).toContain("description: CVE Lite CLI vulnerability analysis"); expect(content).toContain("alwaysApply: false"); expect(content).toContain(SKILL_CONTENT); }); @@ -85,7 +89,7 @@ describe("installSkill", () => { installSkill(tmpDir); const content = fs.readFileSync( path.join(tmpDir, ".github", "copilot-instructions.md"), - "utf-8" + "utf-8", ); expect(content).toContain("## CVE Lite CLI"); expect(content).toContain(SKILL_CONTENT); @@ -98,7 +102,7 @@ describe("installSkill", () => { installSkill(tmpDir); const content = fs.readFileSync( path.join(tmpDir, ".claude", "commands", "cve-lite.md"), - "utf-8" + "utf-8", ); expect(content.match(/Run this skill with \/cve-lite/g)).toHaveLength(1); }); @@ -108,7 +112,7 @@ describe("installSkill", () => { installSkill(tmpDir); const content = fs.readFileSync( path.join(tmpDir, ".cursor", "rules", "cve-lite.mdc"), - "utf-8" + "utf-8", ); expect(content.match(/description: CVE Lite CLI/g)).toHaveLength(1); }); @@ -132,7 +136,7 @@ describe("installSkill", () => { installSkill(tmpDir); const content = fs.readFileSync( path.join(tmpDir, ".github", "copilot-instructions.md"), - "utf-8" + "utf-8", ); expect(content.match(/## CVE Lite CLI/g)).toHaveLength(1); }); @@ -151,7 +155,7 @@ describe("installSkill", () => { expect(content).toContain("## CVE Lite CLI"); expect(content).toContain(SKILL_CONTENT); expect(content.indexOf("Some existing guidance.")).toBeLessThan( - content.indexOf("## CVE Lite CLI") + content.indexOf("## CVE Lite CLI"), ); }); diff --git a/tests/jest-smoke.test.ts b/tests/jest-smoke.test.ts index 690237ed..557e6a7c 100644 --- a/tests/jest-smoke.test.ts +++ b/tests/jest-smoke.test.ts @@ -4,7 +4,13 @@ describe("Jest setup", () => { it("runs TypeScript ESM tests against project modules", () => { expect(chunk([1, 2, 3], 2)).toEqual([[1, 2], [3]]); expect(unique(["a", "a", "b"])).toEqual(["a", "b"]); - expect(uniquePathArrays([["root", "dep"], ["root", "dep"], ["root", "other"]])).toEqual([ + expect( + uniquePathArrays([ + ["root", "dep"], + ["root", "dep"], + ["root", "other"], + ]), + ).toEqual([ ["root", "dep"], ["root", "other"], ]); diff --git a/tests/local-advisory-source.test.ts b/tests/local-advisory-source.test.ts index 37affbc4..679bf865 100644 --- a/tests/local-advisory-source.test.ts +++ b/tests/local-advisory-source.test.ts @@ -22,7 +22,10 @@ function createPackage(name: string, version: string): PackageRef { }; } -function seedVulnerability(db: LocalAdvisoryDatabase, overrides?: Partial): OsvVuln { +function seedVulnerability( + db: LocalAdvisoryDatabase, + overrides?: Partial, +): OsvVuln { const vuln: OsvVuln = { id: "OSV-2026-LOCAL-1", aliases: ["CVE-2026-1234"], @@ -36,10 +39,7 @@ function seedVulnerability(db: LocalAdvisoryDatabase, overrides?: Partial { ranges: [ { type: "ECOSYSTEM", - events: [ - { introduced: "0" }, - { last_affected: "0.2.3" }, - ], + events: [{ introduced: "0" }, { last_affected: "0.2.3" }], }, ], }, @@ -121,7 +118,9 @@ describe("LocalAdvisorySource", () => { createPackage("minimist", "0.2.4"), ]); - expect(results[0]?.vulnerabilities).toEqual([{ id: "OSV-2026-LAST-AFFECTED" }]); + expect(results[0]?.vulnerabilities).toEqual([ + { id: "OSV-2026-LAST-AFFECTED" }, + ]); expect(results[1]?.vulnerabilities).toEqual([]); } finally { db.close(); @@ -168,12 +167,14 @@ describe("LocalAdvisorySource", () => { try { db.setMetadata({ lastSyncAt: "2026-04-04T00:00:00.000Z", - sourceUrl: "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", + sourceUrl: + "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", }); expect(db.getMetadata()).toEqual({ lastSyncAt: "2026-04-04T00:00:00.000Z", - sourceUrl: "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", + sourceUrl: + "https://storage.googleapis.com/osv-vulnerabilities/npm/all.zip", }); } finally { db.close(); diff --git a/tests/lowest-safe-version.test.ts b/tests/lowest-safe-version.test.ts index 8b1c9fe7..9170518b 100644 --- a/tests/lowest-safe-version.test.ts +++ b/tests/lowest-safe-version.test.ts @@ -12,12 +12,19 @@ function mockPackument(versions: string[]) { fetchMock.mockResolvedValue({ ok: true, json: async () => ({ - versions: Object.fromEntries(versions.map(version => [version, {}])), + versions: Object.fromEntries(versions.map((version) => [version, {}])), }), }); } -function createVuln(id: string, events: Array<{ introduced?: string; fixed?: string; last_affected?: string }>): OsvVuln { +function createVuln( + id: string, + events: Array<{ + introduced?: string; + fixed?: string; + last_affected?: string; + }>, +): OsvVuln { return { id, affected: [ @@ -45,7 +52,11 @@ describe("resolveLowestKnownNonVulnerableVersion", () => { createVuln("OSV-2", [{ introduced: "1.0.2" }, { fixed: "1.0.4" }]), ]; - const result = await resolveLowestKnownNonVulnerableVersion("tar", "1.0.0", vulnerabilities); + const result = await resolveLowestKnownNonVulnerableVersion( + "tar", + "1.0.0", + vulnerabilities, + ); expect(result.resolvedVersion).toBe("1.0.4"); expect(result.note).toBeNull(); @@ -62,7 +73,11 @@ describe("resolveLowestKnownNonVulnerableVersion", () => { createVuln("OSV-B", [{ introduced: "6.2.3" }, { fixed: "6.2.6" }]), ]; - const result = await resolveLowestKnownNonVulnerableVersion("tar", "6.2.1", vulnerabilities); + const result = await resolveLowestKnownNonVulnerableVersion( + "tar", + "6.2.1", + vulnerabilities, + ); expect(result.resolvedVersion).toBe("6.2.6"); expect(result.note).toBeNull(); @@ -89,7 +104,11 @@ describe("resolveLowestKnownNonVulnerableVersion", () => { }, ]; - const result = await resolveLowestKnownNonVulnerableVersion("tar", "2.0.0", vulnerabilities); + const result = await resolveLowestKnownNonVulnerableVersion( + "tar", + "2.0.0", + vulnerabilities, + ); expect(result.resolvedVersion).toBeNull(); expect(result.note).toContain("incomplete"); diff --git a/tests/multi-folder-printer.test.ts b/tests/multi-folder-printer.test.ts index 3f49361b..500bf999 100644 --- a/tests/multi-folder-printer.test.ts +++ b/tests/multi-folder-printer.test.ts @@ -41,7 +41,10 @@ beforeAll(async () => { printMultiFolderResults = mod.printMultiFolderResults; }); -const makeResult = (subfolder: string, overrides?: Record) => ({ +const makeResult = ( + subfolder: string, + overrides?: Record, +) => ({ subfolder, sorted: [], scanInput: { @@ -65,20 +68,29 @@ const baseOptions = { failOn: "none", batchSize: "100" } as any; describe("printMultiFolderResults — compact mode (default)", () => { it("prints a folder header for each result", () => { - printMultiFolderResults([makeResult("sessionManager"), makeResult("apiServer")], baseOptions); + printMultiFolderResults( + [makeResult("sessionManager"), makeResult("apiServer")], + baseOptions, + ); const output = consoleLogMock.mock.calls.flat().join("\n"); expect(output).toContain("sessionManager"); expect(output).toContain("apiServer"); }); it("prints summary line with folder count", () => { - printMultiFolderResults([makeResult("sessionManager"), makeResult("apiServer")], baseOptions); + printMultiFolderResults( + [makeResult("sessionManager"), makeResult("apiServer")], + baseOptions, + ); const output = consoleLogMock.mock.calls.flat().join("\n"); expect(output).toMatch(/2 folders/i); }); it("calls printCompactOutput once per folder", () => { - printMultiFolderResults([makeResult("sessionManager"), makeResult("apiServer")], baseOptions); + printMultiFolderResults( + [makeResult("sessionManager"), makeResult("apiServer")], + baseOptions, + ); expect(printCompactOutputMock).toHaveBeenCalledTimes(2); }); @@ -112,7 +124,10 @@ describe("printMultiFolderResults — compact mode (default)", () => { }, }); printMultiFolderResults([result], baseOptions); - expect(logWarnMock).toHaveBeenCalledWith("Nested lockfile fallback used", expect.anything()); + expect(logWarnMock).toHaveBeenCalledWith( + "Nested lockfile fallback used", + expect.anything(), + ); }); }); @@ -168,7 +183,9 @@ describe("printMultiFolderResults — verbose mode", () => { }, }); printMultiFolderResults([result], verboseOptions); - expect(printSkippedDependenciesMock).toHaveBeenCalledWith(["dependencies:debug@^4.3.0"]); + expect(printSkippedDependenciesMock).toHaveBeenCalledWith([ + "dependencies:debug@^4.3.0", + ]); }); it("does not call printSkippedDependencies when skippedDependencies is empty", () => { diff --git a/tests/multi-folder-scan.test.ts b/tests/multi-folder-scan.test.ts index 94001faf..a667e6bd 100644 --- a/tests/multi-folder-scan.test.ts +++ b/tests/multi-folder-scan.test.ts @@ -49,14 +49,18 @@ jest.unstable_mockModule("../src/output/multi-folder-printer.js", () => ({ })); jest.unstable_mockModule("../src/output/multi-folder-html-reporter.js", () => ({ - writeMultiFolderHtmlReport: jest.fn(() => Promise.resolve({ reportPath: "/tmp/report/index.html" })), + writeMultiFolderHtmlReport: jest.fn(() => + Promise.resolve({ reportPath: "/tmp/report/index.html" }), + ), })); jest.unstable_mockModule("../src/utils/version-info.js", () => ({ getCliVersion: jest.fn(() => "1.18.1"), })); -const stdoutWriteSpy = jest.spyOn(process.stdout, "write").mockImplementation(() => true); +const stdoutWriteSpy = jest + .spyOn(process.stdout, "write") + .mockImplementation(() => true); const consoleLogMock = jest.spyOn(console, "log").mockImplementation(() => {}); afterEach(() => jest.clearAllMocks()); @@ -113,7 +117,11 @@ describe("runMultiFolderScan", () => { { subfolder: "apiServer", scanInput: makeScanInput() }, ]); - await runMultiFolderScan({ projectRoot: "/project", batchSize: 100, options: baseOptions }); + await runMultiFolderScan({ + projectRoot: "/project", + batchSize: 100, + options: baseOptions, + }); expect(scanPackagesMock).toHaveBeenCalledTimes(2); }); @@ -125,7 +133,11 @@ describe("runMultiFolderScan", () => { { subfolder: "apiServer", scanInput: makeScanInput() }, ]); - const results = await runMultiFolderScan({ projectRoot: "/project", batchSize: 100, options: baseOptions }); + const results = await runMultiFolderScan({ + projectRoot: "/project", + batchSize: 100, + options: baseOptions, + }); expect(results).toHaveLength(1); expect(results[0].subfolder).toBe("apiServer"); @@ -137,9 +149,15 @@ describe("runMultiFolderScan", () => { { subfolder: "apiServer", scanInput: makeScanInput() }, ]); - await runMultiFolderScan({ projectRoot: "/project", batchSize: 100, options: baseOptions }); + await runMultiFolderScan({ + projectRoot: "/project", + batchSize: 100, + options: baseOptions, + }); - const written = stdoutWriteSpy.mock.calls.map(call => String(call[0])).join(""); + const written = stdoutWriteSpy.mock.calls + .map((call) => String(call[0])) + .join(""); expect(written).toContain("sessionManager/"); expect(written).toContain("apiServer/"); }); @@ -155,7 +173,9 @@ describe("runMultiFolderScan", () => { options: { ...baseOptions, json: true }, }); - const written = stdoutWriteSpy.mock.calls.map(call => String(call[0])).join(""); + const written = stdoutWriteSpy.mock.calls + .map((call) => String(call[0])) + .join(""); expect(written).not.toContain("sessionManager/"); }); @@ -163,9 +183,18 @@ describe("runMultiFolderScan", () => { loadMultiplePackagesMock.mockReturnValue([ { subfolder: "sessionManager", scanInput: makeScanInput() }, ]); - buildSuggestedFixCommandPlanMock.mockReturnValue({ sections: [], targets: [], skipped: [], command: "npm install" }); + buildSuggestedFixCommandPlanMock.mockReturnValue({ + sections: [], + targets: [], + skipped: [], + command: "npm install", + }); - await runMultiFolderScan({ projectRoot: "/project", batchSize: 100, options: baseOptions }); + await runMultiFolderScan({ + projectRoot: "/project", + batchSize: 100, + options: baseOptions, + }); expect(buildSuggestedFixCommandPlanMock).toHaveBeenCalledWith( expect.anything(), diff --git a/tests/multi-package.test.ts b/tests/multi-package.test.ts index 7a6c048a..dca8b636 100644 --- a/tests/multi-package.test.ts +++ b/tests/multi-package.test.ts @@ -1,7 +1,11 @@ import fs from "node:fs"; import path from "node:path"; import os from "node:os"; -import { hasRootLockfile, findNestedLockfiles, loadMultiplePackages } from "../src/parsers/multi-package.js"; +import { + hasRootLockfile, + findNestedLockfiles, + loadMultiplePackages, +} from "../src/parsers/multi-package.js"; import { removeDir } from "./test-utils.js"; function makeTempDir(): string { @@ -43,8 +47,8 @@ describe("findNestedLockfiles", () => { fs.writeFileSync(path.join(b, "package-lock.json"), "{}"); const result = findNestedLockfiles(dir, 4); expect(result).toHaveLength(2); - expect(result.some(f => f.includes("a"))).toBe(true); - expect(result.some(f => f.includes("b"))).toBe(true); + expect(result.some((f) => f.includes("a"))).toBe(true); + expect(result.some((f) => f.includes("b"))).toBe(true); removeDir(dir); }); @@ -83,25 +87,39 @@ describe("loadMultiplePackages", () => { const a = path.join(dir, "a"); fs.mkdirSync(a); // Minimal valid package-lock.json v3 - fs.writeFileSync(path.join(a, "package-lock.json"), JSON.stringify({ - lockfileVersion: 3, - packages: { - "": { dependencies: { "lodash": "4.17.20" } }, - "node_modules/lodash": { version: "4.17.20", resolved: "", integrity: "" } - } - })); + fs.writeFileSync( + path.join(a, "package-lock.json"), + JSON.stringify({ + lockfileVersion: 3, + packages: { + "": { dependencies: { lodash: "4.17.20" } }, + "node_modules/lodash": { + version: "4.17.20", + resolved: "", + integrity: "", + }, + }, + }), + ); const b = path.join(dir, "b"); fs.mkdirSync(b); - fs.writeFileSync(path.join(b, "package-lock.json"), JSON.stringify({ - lockfileVersion: 3, - packages: { - "": { dependencies: { "axios": "1.7.7" } }, - "node_modules/axios": { version: "1.7.7", resolved: "", integrity: "" } - } - })); + fs.writeFileSync( + path.join(b, "package-lock.json"), + JSON.stringify({ + lockfileVersion: 3, + packages: { + "": { dependencies: { axios: "1.7.7" } }, + "node_modules/axios": { + version: "1.7.7", + resolved: "", + integrity: "", + }, + }, + }), + ); const result = loadMultiplePackages(dir, false, 4); expect(result).toHaveLength(2); - const subfolders = result.map(r => r.subfolder).sort(); + const subfolders = result.map((r) => r.subfolder).sort(); expect(subfolders).toEqual(["a", "b"]); removeDir(dir); }); diff --git a/tests/network.test.ts b/tests/network.test.ts index 4cc5c4f0..f39d1cdd 100644 --- a/tests/network.test.ts +++ b/tests/network.test.ts @@ -1,15 +1,25 @@ -import { isLikelyBlockedAdvisoryRequestError, isRateLimitError, isServerError, isSslCertificateError, extractErrorMessage } from "../src/utils/network.js"; +import { + isLikelyBlockedAdvisoryRequestError, + isRateLimitError, + isServerError, + isSslCertificateError, + extractErrorMessage, +} from "../src/utils/network.js"; describe("extractErrorMessage", () => { it("returns the message of a plain Error", () => { - expect(extractErrorMessage(new Error("something went wrong"))).toBe("something went wrong"); + expect(extractErrorMessage(new Error("something went wrong"))).toBe( + "something went wrong", + ); }); it("walks the cause chain to surface nested SSL errors", () => { const cause = new Error("self signed certificate in certificate chain"); const outer = new Error("fetch failed"); (outer as any).cause = cause; - expect(extractErrorMessage(outer)).toBe("fetch failed: self signed certificate in certificate chain"); + expect(extractErrorMessage(outer)).toBe( + "fetch failed: self signed certificate in certificate chain", + ); }); it("returns String(error) for non-Error values", () => { @@ -19,31 +29,64 @@ describe("extractErrorMessage", () => { describe("isSslCertificateError", () => { it("returns true when error code is a known SSL code", () => { - const err = Object.assign(new Error("fetch failed"), { code: "SELF_SIGNED_CERT_IN_CHAIN" }); + const err = Object.assign(new Error("fetch failed"), { + code: "SELF_SIGNED_CERT_IN_CHAIN", + }); expect(isSslCertificateError(err)).toBe(true); }); it("returns true when SSL code is on error.cause", () => { - const cause = Object.assign(new Error("self-signed certificate in certificate chain"), { code: "SELF_SIGNED_CERT_IN_CHAIN" }); + const cause = Object.assign( + new Error("self-signed certificate in certificate chain"), + { code: "SELF_SIGNED_CERT_IN_CHAIN" }, + ); const outer = Object.assign(new Error("fetch failed"), { cause }); expect(isSslCertificateError(outer)).toBe(true); }); it("returns true for all known SSL error codes", () => { - for (const code of ["CERT_UNTRUSTED", "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "DEPTH_ZERO_SELF_SIGNED_CERT", "CERT_HAS_EXPIRED", "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"]) { - expect(isSslCertificateError(Object.assign(new Error("x"), { code }))).toBe(true); + for (const code of [ + "CERT_UNTRUSTED", + "UNABLE_TO_VERIFY_LEAF_SIGNATURE", + "DEPTH_ZERO_SELF_SIGNED_CERT", + "CERT_HAS_EXPIRED", + "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", + ]) { + expect( + isSslCertificateError(Object.assign(new Error("x"), { code })), + ).toBe(true); } }); it("falls back to message matching when no code is present", () => { - expect(isSslCertificateError(new Error("self signed certificate in certificate chain"))).toBe(true); - expect(isSslCertificateError(new Error("self-signed certificate in certificate chain"))).toBe(true); - expect(isSslCertificateError(new Error("certificate has expired"))).toBe(true); - expect(isSslCertificateError(new Error("unable to verify the first certificate"))).toBe(true); + expect( + isSslCertificateError( + new Error("self signed certificate in certificate chain"), + ), + ).toBe(true); + expect( + isSslCertificateError( + new Error("self-signed certificate in certificate chain"), + ), + ).toBe(true); + expect(isSslCertificateError(new Error("certificate has expired"))).toBe( + true, + ); + expect( + isSslCertificateError( + new Error("unable to verify the first certificate"), + ), + ).toBe(true); }); it("returns false for unrelated network errors", () => { - expect(isSslCertificateError(Object.assign(new Error("connection refused"), { code: "ECONNREFUSED" }))).toBe(false); + expect( + isSslCertificateError( + Object.assign(new Error("connection refused"), { + code: "ECONNREFUSED", + }), + ), + ).toBe(false); expect(isSslCertificateError(new Error("fetch failed"))).toBe(false); expect(isSslCertificateError(new Error("403 Forbidden"))).toBe(false); expect(isSslCertificateError("not an error")).toBe(false); @@ -66,7 +109,11 @@ describe("isLikelyBlockedAdvisoryRequestError", () => { }); it("returns false for non-OSV errors", () => { - expect(isLikelyBlockedAdvisoryRequestError("Invalid value for --osv-url: not-a-url")).toBe(false); + expect( + isLikelyBlockedAdvisoryRequestError( + "Invalid value for --osv-url: not-a-url", + ), + ).toBe(false); }); it("returns false for OSV errors that do not look like blocked network access", () => { diff --git a/tests/osv-advisory-source.test.ts b/tests/osv-advisory-source.test.ts index 4fe69af5..200e51ee 100644 --- a/tests/osv-advisory-source.test.ts +++ b/tests/osv-advisory-source.test.ts @@ -30,34 +30,34 @@ describe("OsvAdvisorySource", () => { fetchMock.mockResolvedValue({ ok: true, json: async () => ({ - results: [ - { vulns: [{ id: "OSV-123" }] }, - {}, - ], + results: [{ vulns: [{ id: "OSV-123" }] }, {}], }), }); const source = new OsvAdvisorySource("https://example.test"); const results = await source.queryBatch(packages); - expect(fetchMock).toHaveBeenCalledWith("https://example.test/v1/querybatch", { - method: "POST", - headers: { - "Content-Type": "application/json", + expect(fetchMock).toHaveBeenCalledWith( + "https://example.test/v1/querybatch", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + queries: [ + { + package: { ecosystem: "npm", name: "lodash" }, + version: "4.17.20", + }, + { + package: { ecosystem: "npm", name: "@scope/pkg" }, + version: "1.2.3", + }, + ], + }), }, - body: JSON.stringify({ - queries: [ - { - package: { ecosystem: "npm", name: "lodash" }, - version: "4.17.20", - }, - { - package: { ecosystem: "npm", name: "@scope/pkg" }, - version: "1.2.3", - }, - ], - }), - }); + ); expect(results).toEqual([ { diff --git a/tests/osv-cache.test.ts b/tests/osv-cache.test.ts index bce56c62..c83df6fb 100644 --- a/tests/osv-cache.test.ts +++ b/tests/osv-cache.test.ts @@ -96,13 +96,21 @@ describe("OSV cache", () => { }); it("isEntryStale returns true when cachedAt is more than 30 minutes ago", () => { - const thirtyOneMinutesAgo = new Date(Date.now() - 31 * 60 * 1000).toISOString(); - expect(isEntryStale({ cachedAt: thirtyOneMinutesAgo }, Date.now())).toBe(true); + const thirtyOneMinutesAgo = new Date( + Date.now() - 31 * 60 * 1000, + ).toISOString(); + expect(isEntryStale({ cachedAt: thirtyOneMinutesAgo }, Date.now())).toBe( + true, + ); }); it("isEntryStale returns false when cachedAt is less than 30 minutes ago", () => { - const twentyNineMinutesAgo = new Date(Date.now() - 29 * 60 * 1000).toISOString(); - expect(isEntryStale({ cachedAt: twentyNineMinutesAgo }, Date.now())).toBe(false); + const twentyNineMinutesAgo = new Date( + Date.now() - 29 * 60 * 1000, + ).toISOString(); + expect(isEntryStale({ cachedAt: twentyNineMinutesAgo }, Date.now())).toBe( + false, + ); }); it("isEntryStale returns false for a freshly written entry", () => { diff --git a/tests/osv-sync.test.ts b/tests/osv-sync.test.ts index fb3c1de4..93f36ebc 100644 --- a/tests/osv-sync.test.ts +++ b/tests/osv-sync.test.ts @@ -3,7 +3,10 @@ import os from "node:os"; import path from "node:path"; import { zipSync } from "fflate"; import { LocalAdvisoryDatabase } from "../src/advisory/local-db.js"; -import { getDefaultAdvisoryDbPath, syncOsvAdvisories } from "../src/advisory/osv-sync.js"; +import { + getDefaultAdvisoryDbPath, + syncOsvAdvisories, +} from "../src/advisory/osv-sync.js"; function createTempDir(): string { return fs.mkdtempSync(path.join(os.tmpdir(), "cve-lite-osv-sync-")); @@ -45,7 +48,9 @@ describe("syncOsvAdvisories", () => { affected: [ { package: { ecosystem: "npm", name: "minimist" }, - ranges: [{ events: [{ introduced: "0" }, { last_affected: "0.2.3" }] }], + ranges: [ + { events: [{ introduced: "0" }, { last_affected: "0.2.3" }] }, + ], }, ], }, @@ -56,7 +61,7 @@ describe("syncOsvAdvisories", () => { const result = await syncOsvAdvisories({ outputPath: dbPath, sourceUrl: "https://mirror.example/npm/all.zip", - onProgress: event => { + onProgress: (event) => { progressEvents.push(`${event.phase}:${event.message}`); }, fetchImpl: async () => @@ -79,10 +84,24 @@ describe("syncOsvAdvisories", () => { sourceUrl: "https://mirror.example/npm/all.zip", }); expect(progressEvents[0]).toContain("init:Sync initiated"); - expect(progressEvents.some(event => event.startsWith("download:Downloading advisory dump:"))).toBe(true); - expect(progressEvents.some(event => event.startsWith("extract:Archive loaded."))).toBe(true); - expect(progressEvents.some(event => event.startsWith("ingest:Processing advisory records: 2 / 2"))).toBe(true); - expect(progressEvents[progressEvents.length - 1]).toContain("complete:Sync complete."); + expect( + progressEvents.some((event) => + event.startsWith("download:Downloading advisory dump:"), + ), + ).toBe(true); + expect( + progressEvents.some((event) => + event.startsWith("extract:Archive loaded."), + ), + ).toBe(true); + expect( + progressEvents.some((event) => + event.startsWith("ingest:Processing advisory records: 2 / 2"), + ), + ).toBe(true); + expect(progressEvents[progressEvents.length - 1]).toContain( + "complete:Sync complete.", + ); const db = new LocalAdvisoryDatabase(dbPath, { readonly: true }); try { @@ -91,9 +110,27 @@ describe("syncOsvAdvisories", () => { sourceUrl: "https://mirror.example/npm/all.zip", }); expect(db.getVulnerability("OSV-1")).toMatchObject({ id: "OSV-1" }); - expect(db.findMatchingVulnerabilityIds({ ecosystem: "npm", name: "lodash", version: "4.17.20" })).toEqual(["OSV-1"]); - expect(db.findMatchingVulnerabilityIds({ ecosystem: "npm", name: "lodash", version: "4.17.21" })).toEqual([]); - expect(db.findMatchingVulnerabilityIds({ ecosystem: "npm", name: "minimist", version: "0.2.3" })).toEqual(["OSV-2"]); + expect( + db.findMatchingVulnerabilityIds({ + ecosystem: "npm", + name: "lodash", + version: "4.17.20", + }), + ).toEqual(["OSV-1"]); + expect( + db.findMatchingVulnerabilityIds({ + ecosystem: "npm", + name: "lodash", + version: "4.17.21", + }), + ).toEqual([]); + expect( + db.findMatchingVulnerabilityIds({ + ecosystem: "npm", + name: "minimist", + version: "0.2.3", + }), + ).toEqual(["OSV-2"]); } finally { db.close(); } @@ -122,7 +159,9 @@ describe("syncOsvAdvisories", () => { affected: [ { package: { ecosystem: "npm", name: "debug" }, - ranges: [{ events: [{ introduced: "0" }, { fixed: "4.3.1" }] }], + ranges: [ + { events: [{ introduced: "0" }, { fixed: "4.3.1" }] }, + ], }, ], }, @@ -183,12 +222,18 @@ describe("syncOsvAdvisories", () => { const db = new LocalAdvisoryDatabase(dbPath, { readonly: true }); try { - expect(db.getVulnerability("GHSA-active")).toMatchObject({ id: "GHSA-active" }); + expect(db.getVulnerability("GHSA-active")).toMatchObject({ + id: "GHSA-active", + }); expect(db.getVulnerability("GHSA-withdrawn")).toBeNull(); // The withdrawn record must not match a scan against the same package/version // it was previously reported against. expect( - db.findMatchingVulnerabilityIds({ ecosystem: "npm", name: "lodash", version: "4.17.20" }), + db.findMatchingVulnerabilityIds({ + ecosystem: "npm", + name: "lodash", + version: "4.17.20", + }), ).toEqual(["GHSA-active"]); } finally { db.close(); @@ -217,9 +262,13 @@ describe("syncOsvAdvisories", () => { const advisoryPath = getDefaultAdvisoryDbPath(); if (process.platform === "win32") { - expect(advisoryPath.toLowerCase()).toContain(path.join("cve-lite", "advisories.db").toLowerCase()); + expect(advisoryPath.toLowerCase()).toContain( + path.join("cve-lite", "advisories.db").toLowerCase(), + ); } else { - expect(advisoryPath).toBe(path.join(os.homedir(), ".cache", "cve-lite", "advisories.db")); + expect(advisoryPath).toBe( + path.join(os.homedir(), ".cache", "cve-lite", "advisories.db"), + ); } }); }); diff --git a/tests/output.test.ts b/tests/output.test.ts index ef89c503..1d247e4d 100644 --- a/tests/output.test.ts +++ b/tests/output.test.ts @@ -15,7 +15,10 @@ import { summarizeRisk, formatRelLabel, } from "../src/output/formatters.js"; -import { buildSuggestedFixCommandPlan, findSuggestedCommandForFinding } from "../src/remediation/fix-commands.js"; +import { + buildSuggestedFixCommandPlan, + findSuggestedCommandForFinding, +} from "../src/remediation/fix-commands.js"; import { printActionSummary, printCompactOutput, @@ -65,7 +68,9 @@ function createFinding(overrides?: Partial): Finding { }; } -function createScanInput(mode: ScanInput["mode"] = "resolved-lockfile"): ScanInput { +function createScanInput( + mode: ScanInput["mode"] = "resolved-lockfile", +): ScanInput { return { mode, source: mode === "manifest-fallback" ? "package-json" : "package-lock", @@ -98,9 +103,11 @@ function createScanInputForSource(source: ScanInput["source"]): ScanInput { function captureLogs(run: () => void): string[] { const logs: string[] = []; - const spy = jest.spyOn(console, "log").mockImplementation((...args: unknown[]) => { - logs.push(args.map(arg => String(arg)).join(" ")); - }); + const spy = jest + .spyOn(console, "log") + .mockImplementation((...args: unknown[]) => { + logs.push(args.map((arg) => String(arg)).join(" ")); + }); try { run(); @@ -108,14 +115,16 @@ function captureLogs(run: () => void): string[] { spy.mockRestore(); } - return logs.map(line => stripAnsi(line)); + return logs.map((line) => stripAnsi(line)); } function captureErrors(run: () => void): string[] { const logs: string[] = []; - const spy = jest.spyOn(console, "error").mockImplementation((...args: unknown[]) => { - logs.push(args.map(arg => String(arg)).join(" ")); - }); + const spy = jest + .spyOn(console, "error") + .mockImplementation((...args: unknown[]) => { + logs.push(args.map((arg) => String(arg)).join(" ")); + }); try { run(); @@ -134,7 +143,9 @@ describe("output formatters", () => { debug.announcePath(); }); - const appendSpy = jest.spyOn(fs, "appendFileSync").mockImplementation(() => undefined); + const appendSpy = jest + .spyOn(fs, "appendFileSync") + .mockImplementation(() => undefined); try { const enabledLines = captureErrors(() => { const debug = createDebugLogger(true); @@ -144,7 +155,9 @@ describe("output formatters", () => { }); expect(disabledLines).toEqual([]); - expect(enabledLines[0]).toContain("[debug] Writing debug log to ./cve-lite-debug-"); + expect(enabledLines[0]).toContain( + "[debug] Writing debug log to ./cve-lite-debug-", + ); expect(appendSpy).toHaveBeenCalled(); } finally { appendSpy.mockRestore(); @@ -172,7 +185,9 @@ describe("output formatters", () => { const finding = createFinding(); expect(getPrimaryParent(finding)).toBe("app"); - expect(getRecommendedAction(finding)).toContain("Upgrade app from 1.0.0 to 1.1.0"); + expect(getRecommendedAction(finding)).toContain( + "Upgrade app from 1.0.0 to 1.1.0", + ); expect(summarizeRisk(finding)).toContain("specific parent upgrade target"); expect(summarizeNextAction(finding)).toBe("Upgrade app 1.0.0 -> 1.1.0."); }); @@ -227,13 +242,22 @@ describe("output formatters", () => { it("sorts findings by severity and then package name", () => { const findings = [ - createFinding({ pkg: { name: "zlib", version: "1.0.0", ecosystem: "npm" }, severity: "medium" }), - createFinding({ pkg: { name: "axios", version: "1.0.0", ecosystem: "npm" }, severity: "medium" }), - createFinding({ pkg: { name: "chalk", version: "1.0.0", ecosystem: "npm" }, severity: "high" }), + createFinding({ + pkg: { name: "zlib", version: "1.0.0", ecosystem: "npm" }, + severity: "medium", + }), + createFinding({ + pkg: { name: "axios", version: "1.0.0", ecosystem: "npm" }, + severity: "medium", + }), + createFinding({ + pkg: { name: "chalk", version: "1.0.0", ecosystem: "npm" }, + severity: "high", + }), ]; const sorted = sortFindingsForOutput(findings); - expect(sorted.map(item => `${item.severity}:${item.pkg.name}`)).toEqual([ + expect(sorted.map((item) => `${item.severity}:${item.pkg.name}`)).toEqual([ "high:chalk", "medium:axios", "medium:zlib", @@ -241,7 +265,9 @@ describe("output formatters", () => { }); it("prints cache and info/warn lines when output is not json", () => { - const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "cve-lite-output-cache-")); + const cacheDir = fs.mkdtempSync( + path.join(os.tmpdir(), "cve-lite-output-cache-"), + ); fs.writeFileSync( path.join(cacheDir, "osv-vulns.json"), JSON.stringify({ @@ -260,7 +286,9 @@ describe("output formatters", () => { logWarn("careful"); }); - expect(lines[0]).toContain("Cache: 1 package match record, 1 advisory detail record, 1 empty lookup"); + expect(lines[0]).toContain( + "Cache: 1 package match record, 1 advisory detail record, 1 empty lookup", + ); expect(lines[1]).toBe("hello"); expect(lines[2]).toBe("careful"); } finally { @@ -280,7 +308,12 @@ describe("output formatters", () => { it("builds a package-manager-aware fix command plan for urgent findings", () => { const findings = [ createFinding({ - pkg: { name: "minimist", version: "0.0.8", ecosystem: "npm", paths: [["project", "minimist"]] }, + pkg: { + name: "minimist", + version: "0.0.8", + ecosystem: "npm", + paths: [["project", "minimist"]], + }, relationship: "direct", dependencyPaths: [["project", "minimist"]], severity: "critical", @@ -288,7 +321,12 @@ describe("output formatters", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "lodash", version: "4.17.20", ecosystem: "npm", paths: [["project", "app", "lodash"]] }, + pkg: { + name: "lodash", + version: "4.17.20", + ecosystem: "npm", + paths: [["project", "app", "lodash"]], + }, relationship: "transitive", dependencyPaths: [["project", "app", "lodash"]], severity: "high", @@ -303,7 +341,12 @@ describe("output formatters", () => { }, }), createFinding({ - pkg: { name: "marsdb", version: "0.6.11", ecosystem: "npm", paths: [["project", "marsdb"]] }, + pkg: { + name: "marsdb", + version: "0.6.11", + ecosystem: "npm", + paths: [["project", "marsdb"]], + }, relationship: "direct", dependencyPaths: [["project", "marsdb"]], severity: "critical", @@ -312,10 +355,22 @@ describe("output formatters", () => { }), ]; - const npmPlan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); - const pnpmPlan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("pnpm-lock")); - const yarnPlan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("yarn-lock")); - const bunPlan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("bun-lock")); + const npmPlan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); + const pnpmPlan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("pnpm-lock"), + ); + const yarnPlan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("yarn-lock"), + ); + const bunPlan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("bun-lock"), + ); expect(npmPlan?.command).toBe("npm install minimist@1.2.8 app@1.1.0"); expect(pnpmPlan?.command).toBe("pnpm add minimist@1.2.8 app@1.1.0"); @@ -336,7 +391,8 @@ describe("output formatters", () => { expect(npmPlan?.skipped).toEqual([ expect.objectContaining({ package: "marsdb", - reason: "No safe upgrade target is known for this urgent direct dependency.", + reason: + "No safe upgrade target is known for this urgent direct dependency.", }), ]); }); @@ -344,7 +400,12 @@ describe("output formatters", () => { it("includes additional direct and parent upgrade commands beyond urgent findings", () => { const findings = [ createFinding({ - pkg: { name: "minimist", version: "0.0.8", ecosystem: "npm", paths: [["project", "minimist"]] }, + pkg: { + name: "minimist", + version: "0.0.8", + ecosystem: "npm", + paths: [["project", "minimist"]], + }, relationship: "direct", dependencyPaths: [["project", "minimist"]], severity: "high", @@ -352,7 +413,12 @@ describe("output formatters", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "tar", version: "6.2.1", ecosystem: "npm", paths: [["project", "tar"]] }, + pkg: { + name: "tar", + version: "6.2.1", + ecosystem: "npm", + paths: [["project", "tar"]], + }, relationship: "direct", dependencyPaths: [["project", "tar"]], severity: "medium", @@ -360,7 +426,12 @@ describe("output formatters", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "diff", version: "2.2.3", ecosystem: "npm", paths: [["project", "gulp-diff", "diff"]] }, + pkg: { + name: "diff", + version: "2.2.3", + ecosystem: "npm", + paths: [["project", "gulp-diff", "diff"]], + }, relationship: "transitive", dependencyPaths: [["project", "gulp-diff", "diff"]], severity: "medium", @@ -377,7 +448,10 @@ describe("output formatters", () => { }), ]; - const plan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); + const plan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); expect(plan?.sections).toEqual([ expect.objectContaining({ @@ -401,7 +475,12 @@ describe("output formatters", () => { it("builds an npm update command for transitive findings resolvable within the current parent range", () => { const findings = [ createFinding({ - pkg: { name: "diff", version: "5.0.0", ecosystem: "npm", paths: [["project", "mocha", "diff"]] }, + pkg: { + name: "diff", + version: "5.0.0", + ecosystem: "npm", + paths: [["project", "mocha", "diff"]], + }, relationship: "transitive", dependencyPaths: [["project", "mocha", "diff"]], severity: "high", @@ -413,13 +492,20 @@ describe("output formatters", () => { currentVersion: "10.0.0", targetChildVersion: "5.1.0", viaPath: ["project", "mocha", "diff"], - reason: "mocha@10.0.0 already allows diff@5.1.0 within the current dependency range", + reason: + "mocha@10.0.0 already allows diff@5.1.0 within the current dependency range", }, }), ]; - const npmPlan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); - const pnpmPlan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("pnpm-lock")); + const npmPlan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); + const pnpmPlan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("pnpm-lock"), + ); expect(npmPlan?.command).toBe("npm update mocha"); expect(npmPlan?.sections).toEqual([ @@ -459,7 +545,12 @@ describe("output formatters", () => { it("skips fixed-version hints that are not real upgrades", () => { const findings = [ createFinding({ - pkg: { name: "diff", version: "4.0.2", ecosystem: "npm", paths: [["project", "diff"]] }, + pkg: { + name: "diff", + version: "4.0.2", + ecosystem: "npm", + paths: [["project", "diff"]], + }, relationship: "direct", dependencyPaths: [["project", "diff"]], severity: "medium", @@ -468,13 +559,17 @@ describe("output formatters", () => { }), ]; - const plan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); + const plan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); expect(plan?.sections).toEqual([]); expect(plan?.skipped).toEqual([ expect.objectContaining({ package: "diff", - reason: "Fixed-version hint 3.5.1 is not an upgrade from installed 4.0.2.", + reason: + "Fixed-version hint 3.5.1 is not an upgrade from installed 4.0.2.", }), ]); }); @@ -482,7 +577,12 @@ describe("output formatters", () => { it("keeps exact published fixed versions in the normal direct section", () => { const findings = [ createFinding({ - pkg: { name: "tar", version: "7.5.4", ecosystem: "npm", paths: [["project", "tar"]] }, + pkg: { + name: "tar", + version: "7.5.4", + ecosystem: "npm", + paths: [["project", "tar"]], + }, relationship: "direct", dependencyPaths: [["project", "tar"]], severity: "medium", @@ -493,7 +593,10 @@ describe("output formatters", () => { }), ]; - const plan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); + const plan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); expect(plan?.sections).toEqual([ expect.objectContaining({ @@ -502,13 +605,20 @@ describe("output formatters", () => { command: "npm install tar@7.5.7", }), ]); - expect(plan?.sections.find(section => section.kind === "direct-adjusted")).toBeUndefined(); + expect( + plan?.sections.find((section) => section.kind === "direct-adjusted"), + ).toBeUndefined(); }); it("moves nearest-published fallbacks into the registry-adjusted section with notes", () => { const findings = [ createFinding({ - pkg: { name: "lodash.template", version: "3.6.2", ecosystem: "npm", paths: [["project", "lodash.template"]] }, + pkg: { + name: "lodash.template", + version: "3.6.2", + ecosystem: "npm", + paths: [["project", "lodash.template"]], + }, relationship: "direct", dependencyPaths: [["project", "lodash.template"]], severity: "low", @@ -520,7 +630,10 @@ describe("output formatters", () => { }), ]; - const plan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); + const plan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); expect(plan?.sections).toEqual([ expect.objectContaining({ @@ -543,7 +656,12 @@ describe("output formatters", () => { it("keeps normal direct fixes separate from registry-adjusted fixes", () => { const findings = [ createFinding({ - pkg: { name: "serialize-javascript", version: "7.0.3", ecosystem: "npm", paths: [["project", "serialize-javascript"]] }, + pkg: { + name: "serialize-javascript", + version: "7.0.3", + ecosystem: "npm", + paths: [["project", "serialize-javascript"]], + }, relationship: "direct", dependencyPaths: [["project", "serialize-javascript"]], severity: "low", @@ -553,7 +671,12 @@ describe("output formatters", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "lodash.template", version: "3.6.2", ecosystem: "npm", paths: [["project", "lodash.template"]] }, + pkg: { + name: "lodash.template", + version: "3.6.2", + ecosystem: "npm", + paths: [["project", "lodash.template"]], + }, relationship: "direct", dependencyPaths: [["project", "lodash.template"]], severity: "low", @@ -565,7 +688,10 @@ describe("output formatters", () => { }), ]; - const plan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); + const plan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); expect(plan?.sections).toEqual([ expect.objectContaining({ @@ -597,7 +723,12 @@ describe("output formatters", () => { // command using the advisory hint instead of dropping the target. const findings = [ createFinding({ - pkg: { name: "@angular/compiler", version: "19.2.19", ecosystem: "npm", paths: [["project", "@angular/compiler"]] }, + pkg: { + name: "@angular/compiler", + version: "19.2.19", + ecosystem: "npm", + paths: [["project", "@angular/compiler"]], + }, relationship: "direct", dependencyPaths: [["project", "@angular/compiler"]], severity: "high", @@ -638,7 +769,12 @@ describe("output formatters", () => { // must be skipped — not pushed back into the plan with the advisory hint. const findings = [ createFinding({ - pkg: { name: "@angular/compiler", version: "19.2.19", ecosystem: "npm", paths: [["project", "@angular/compiler"]] }, + pkg: { + name: "@angular/compiler", + version: "19.2.19", + ecosystem: "npm", + paths: [["project", "@angular/compiler"]], + }, relationship: "direct", dependencyPaths: [["project", "@angular/compiler"]], severity: "high", @@ -649,7 +785,10 @@ describe("output formatters", () => { }), ]; - const onlinePlan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); + const onlinePlan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); expect(onlinePlan?.sections).toEqual([]); expect(onlinePlan?.skipped).toEqual([ @@ -720,7 +859,12 @@ describe("output formatters", () => { // vulnerable. The recommendation prose must agree with the fix-command // table — both should reference the validated target. const finding = createFinding({ - pkg: { name: "axios", version: "0.21.1", ecosystem: "npm", paths: [["project", "axios"]] }, + pkg: { + name: "axios", + version: "0.21.1", + ecosystem: "npm", + paths: [["project", "axios"]], + }, relationship: "direct", dependencyPaths: [["project", "axios"]], firstFixedVersion: "0.21.2", @@ -733,7 +877,12 @@ describe("output formatters", () => { it("summarizeNextAction prefers the registry-validated target over the raw advisory hint for direct findings (#302)", () => { const finding = createFinding({ - pkg: { name: "axios", version: "0.21.1", ecosystem: "npm", paths: [["project", "axios"]] }, + pkg: { + name: "axios", + version: "0.21.1", + ecosystem: "npm", + paths: [["project", "axios"]], + }, relationship: "direct", dependencyPaths: [["project", "axios"]], firstFixedVersion: "0.21.2", @@ -751,7 +900,12 @@ describe("output formatters", () => { // exists. The previous "No dependency path found" wording contradicted // the dependency-path section that displayed the same path. const finding = createFinding({ - pkg: { name: "micromatch", version: "4.0.5", ecosystem: "npm", paths: [["project", "micromatch"]] }, + pkg: { + name: "micromatch", + version: "4.0.5", + ecosystem: "npm", + paths: [["project", "micromatch"]], + }, relationship: "transitive", dependencyPaths: [["project", "micromatch"]], recommendedParentUpgrade: undefined, @@ -766,7 +920,12 @@ describe("output formatters", () => { it("skips Tier 2 transitive finding with parent-aware reason", () => { const findings = [ createFinding({ - pkg: { name: "picomatch", version: "2.2.1", ecosystem: "npm", paths: [["project", "lint-staged", "picomatch"]] }, + pkg: { + name: "picomatch", + version: "2.2.1", + ecosystem: "npm", + paths: [["project", "lint-staged", "picomatch"]], + }, relationship: "transitive", dependencyPaths: [["project", "lint-staged", "picomatch"]], severity: "high", @@ -775,7 +934,10 @@ describe("output formatters", () => { }), ]; - const plan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); + const plan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); expect(plan?.skipped).toEqual([ expect.objectContaining({ @@ -784,13 +946,20 @@ describe("output formatters", () => { }), ]); expect(plan?.skipped[0].reason).toContain("2.3.1"); - expect(plan?.skipped[0].reason).not.toBe("No specific parent upgrade target was found for this transitive issue."); + expect(plan?.skipped[0].reason).not.toBe( + "No specific parent upgrade target was found for this transitive issue.", + ); }); it("skips Tier 3 transitive finding with honest no-path reason", () => { const findings = [ createFinding({ - pkg: { name: "picomatch", version: "2.2.1", ecosystem: "npm", paths: [] }, + pkg: { + name: "picomatch", + version: "2.2.1", + ecosystem: "npm", + paths: [], + }, relationship: "transitive", dependencyPaths: [], severity: "high", @@ -799,7 +968,10 @@ describe("output formatters", () => { }), ]; - const plan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); + const plan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); expect(plan?.skipped).toEqual([ expect.objectContaining({ @@ -810,9 +982,20 @@ describe("output formatters", () => { }); describe("dev dependency fix commands", () => { - function createDirectFinding(name: string, version: string, fixedVersion: string, dev: boolean): Finding { + function createDirectFinding( + name: string, + version: string, + fixedVersion: string, + dev: boolean, + ): Finding { return createFinding({ - pkg: { name, version, ecosystem: "npm", dev, paths: [["project", name]] }, + pkg: { + name, + version, + ecosystem: "npm", + dev, + paths: [["project", name]], + }, relationship: "direct", dependencyPaths: [["project", name]], severity: "high", @@ -823,15 +1006,27 @@ describe("output formatters", () => { }); } - function scanInputWithPackages(source: "package-lock" | "pnpm-lock" | "yarn-lock" | "bun-lock", packages: { name: string; version: string; dev: boolean }[]): ScanInput { + function scanInputWithPackages( + source: "package-lock" | "pnpm-lock" | "yarn-lock" | "bun-lock", + packages: { name: string; version: string; dev: boolean }[], + ): ScanInput { return { mode: "resolved-lockfile", source, - filePath: source === "package-lock" ? "/tmp/package-lock.json" - : source === "pnpm-lock" ? "/tmp/pnpm-lock.yaml" - : source === "yarn-lock" ? "/tmp/yarn.lock" - : null, - packages: packages.map(p => ({ name: p.name, version: p.version, ecosystem: "npm", dev: p.dev })), + filePath: + source === "package-lock" + ? "/tmp/package-lock.json" + : source === "pnpm-lock" + ? "/tmp/pnpm-lock.yaml" + : source === "yarn-lock" + ? "/tmp/yarn.lock" + : null, + packages: packages.map((p) => ({ + name: p.name, + version: p.version, + ecosystem: "npm", + dev: p.dev, + })), notes: [], warnings: [], skippedDependencies: [], @@ -840,13 +1035,25 @@ describe("output formatters", () => { it("adds -D to npm install command for a direct dev dependency", () => { const findings = [createDirectFinding("jest", "30.3.0", "30.4.0", true)]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("package-lock", [{ name: "jest", version: "30.3.0", dev: true }])); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("package-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + ]), + ); expect(plan?.command).toBe("npm install -D jest@30.4.0"); }); it("does not add -D for a direct prod dependency", () => { - const findings = [createDirectFinding("lodash", "4.17.20", "4.17.21", false)]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("package-lock", [{ name: "lodash", version: "4.17.20", dev: false }])); + const findings = [ + createDirectFinding("lodash", "4.17.20", "4.17.21", false), + ]; + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("package-lock", [ + { name: "lodash", version: "4.17.20", dev: false }, + ]), + ); expect(plan?.command).toBe("npm install lodash@4.17.21"); }); @@ -855,28 +1062,48 @@ describe("output formatters", () => { createDirectFinding("jest", "30.3.0", "30.4.0", true), createDirectFinding("lodash", "4.17.20", "4.17.21", false), ]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("package-lock", [ - { name: "jest", version: "30.3.0", dev: true }, - { name: "lodash", version: "4.17.20", dev: false }, - ])); - expect(plan?.command).toBe("npm install lodash@4.17.21 && npm install -D jest@30.4.0"); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("package-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + { name: "lodash", version: "4.17.20", dev: false }, + ]), + ); + expect(plan?.command).toBe( + "npm install lodash@4.17.21 && npm install -D jest@30.4.0", + ); }); it("adds -D for pnpm dev dependency", () => { const findings = [createDirectFinding("jest", "30.3.0", "30.4.0", true)]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("pnpm-lock", [{ name: "jest", version: "30.3.0", dev: true }])); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("pnpm-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + ]), + ); expect(plan?.command).toBe("pnpm add -D jest@30.4.0"); }); it("adds -D for yarn dev dependency", () => { const findings = [createDirectFinding("jest", "30.3.0", "30.4.0", true)]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("yarn-lock", [{ name: "jest", version: "30.3.0", dev: true }])); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("yarn-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + ]), + ); expect(plan?.command).toBe("yarn add -D jest@30.4.0"); }); it("adds --dev for bun dev dependency", () => { const findings = [createDirectFinding("jest", "30.3.0", "30.4.0", true)]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("bun-lock", [{ name: "jest", version: "30.3.0", dev: true }])); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("bun-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + ]), + ); expect(plan?.command).toBe("bun add --dev jest@30.4.0"); }); @@ -885,11 +1112,16 @@ describe("output formatters", () => { createDirectFinding("jest", "30.3.0", "30.4.0", true), createDirectFinding("lodash", "4.17.20", "4.17.21", false), ]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("pnpm-lock", [ - { name: "jest", version: "30.3.0", dev: true }, - { name: "lodash", version: "4.17.20", dev: false }, - ])); - expect(plan?.command).toBe("pnpm add lodash@4.17.21 && pnpm add -D jest@30.4.0"); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("pnpm-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + { name: "lodash", version: "4.17.20", dev: false }, + ]), + ); + expect(plan?.command).toBe( + "pnpm add lodash@4.17.21 && pnpm add -D jest@30.4.0", + ); }); it("splits mixed dev and prod targets into two commands for yarn", () => { @@ -897,11 +1129,16 @@ describe("output formatters", () => { createDirectFinding("jest", "30.3.0", "30.4.0", true), createDirectFinding("lodash", "4.17.20", "4.17.21", false), ]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("yarn-lock", [ - { name: "jest", version: "30.3.0", dev: true }, - { name: "lodash", version: "4.17.20", dev: false }, - ])); - expect(plan?.command).toBe("yarn add lodash@4.17.21 && yarn add -D jest@30.4.0"); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("yarn-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + { name: "lodash", version: "4.17.20", dev: false }, + ]), + ); + expect(plan?.command).toBe( + "yarn add lodash@4.17.21 && yarn add -D jest@30.4.0", + ); }); it("splits mixed dev and prod targets into two commands for bun", () => { @@ -909,17 +1146,28 @@ describe("output formatters", () => { createDirectFinding("jest", "30.3.0", "30.4.0", true), createDirectFinding("lodash", "4.17.20", "4.17.21", false), ]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("bun-lock", [ - { name: "jest", version: "30.3.0", dev: true }, - { name: "lodash", version: "4.17.20", dev: false }, - ])); - expect(plan?.command).toBe("bun add lodash@4.17.21 && bun add --dev jest@30.4.0"); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("bun-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + { name: "lodash", version: "4.17.20", dev: false }, + ]), + ); + expect(plan?.command).toBe( + "bun add lodash@4.17.21 && bun add --dev jest@30.4.0", + ); }); it("adds -D for parent upgrade targeting a dev dependency for pnpm", () => { const findings = [ createFinding({ - pkg: { name: "js-yaml", version: "3.14.2", ecosystem: "npm", dev: true, paths: [["project", "jest", "js-yaml"]] }, + pkg: { + name: "js-yaml", + version: "3.14.2", + ecosystem: "npm", + dev: true, + paths: [["project", "jest", "js-yaml"]], + }, relationship: "transitive", dependencyPaths: [["project", "jest", "js-yaml"]], severity: "medium", @@ -936,14 +1184,25 @@ describe("output formatters", () => { recommendedNpmTransitiveRemediation: undefined, }), ]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("pnpm-lock", [{ name: "jest", version: "30.3.0", dev: true }])); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("pnpm-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + ]), + ); expect(plan?.command).toBe("pnpm add -D jest@30.4.0"); }); it("adds -D for parent upgrade targeting a dev dependency for yarn", () => { const findings = [ createFinding({ - pkg: { name: "js-yaml", version: "3.14.2", ecosystem: "npm", dev: true, paths: [["project", "jest", "js-yaml"]] }, + pkg: { + name: "js-yaml", + version: "3.14.2", + ecosystem: "npm", + dev: true, + paths: [["project", "jest", "js-yaml"]], + }, relationship: "transitive", dependencyPaths: [["project", "jest", "js-yaml"]], severity: "medium", @@ -960,14 +1219,25 @@ describe("output formatters", () => { recommendedNpmTransitiveRemediation: undefined, }), ]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("yarn-lock", [{ name: "jest", version: "30.3.0", dev: true }])); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("yarn-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + ]), + ); expect(plan?.command).toBe("yarn add -D jest@30.4.0"); }); it("adds --dev for parent upgrade targeting a dev dependency for bun", () => { const findings = [ createFinding({ - pkg: { name: "js-yaml", version: "3.14.2", ecosystem: "npm", dev: true, paths: [["project", "jest", "js-yaml"]] }, + pkg: { + name: "js-yaml", + version: "3.14.2", + ecosystem: "npm", + dev: true, + paths: [["project", "jest", "js-yaml"]], + }, relationship: "transitive", dependencyPaths: [["project", "jest", "js-yaml"]], severity: "medium", @@ -984,14 +1254,25 @@ describe("output formatters", () => { recommendedNpmTransitiveRemediation: undefined, }), ]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("bun-lock", [{ name: "jest", version: "30.3.0", dev: true }])); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("bun-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + ]), + ); expect(plan?.command).toBe("bun add --dev jest@30.4.0"); }); it("adds -D for parent upgrade targeting a dev dependency", () => { const findings = [ createFinding({ - pkg: { name: "js-yaml", version: "3.14.2", ecosystem: "npm", dev: true, paths: [["project", "jest", "js-yaml"]] }, + pkg: { + name: "js-yaml", + version: "3.14.2", + ecosystem: "npm", + dev: true, + paths: [["project", "jest", "js-yaml"]], + }, relationship: "transitive", dependencyPaths: [["project", "jest", "js-yaml"]], severity: "medium", @@ -1008,20 +1289,37 @@ describe("output formatters", () => { recommendedNpmTransitiveRemediation: undefined, }), ]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("package-lock", [{ name: "jest", version: "30.3.0", dev: true }])); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("package-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + ]), + ); expect(plan?.command).toBe("npm install -D jest@30.4.0"); }); it("findSuggestedCommandForFinding returns -D command for dev dependency target", () => { const findings = [createDirectFinding("jest", "30.3.0", "30.4.0", true)]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("package-lock", [{ name: "jest", version: "30.3.0", dev: true }])); + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("package-lock", [ + { name: "jest", version: "30.3.0", dev: true }, + ]), + ); const command = findSuggestedCommandForFinding(plan!, findings[0]!); expect(command).toBe("npm install -D jest@30.4.0"); }); it("findSuggestedCommandForFinding returns command without -D for prod dependency target", () => { - const findings = [createDirectFinding("lodash", "4.17.20", "4.17.21", false)]; - const plan = buildSuggestedFixCommandPlan(findings, scanInputWithPackages("package-lock", [{ name: "lodash", version: "4.17.20", dev: false }])); + const findings = [ + createDirectFinding("lodash", "4.17.20", "4.17.21", false), + ]; + const plan = buildSuggestedFixCommandPlan( + findings, + scanInputWithPackages("package-lock", [ + { name: "lodash", version: "4.17.20", dev: false }, + ]), + ); const command = findSuggestedCommandForFinding(plan!, findings[0]!); expect(command).toBe("npm install lodash@4.17.21"); }); @@ -1034,12 +1332,14 @@ describe("output formatters", () => { firstFixedVersion: null, recommendedParentUpgrade: undefined, recommendedNpmTransitiveRemediation: undefined, - vulnerabilities: [{ - id: "MAL-2025-21003", - aliases: [], - summary: "Malicious code in fs (npm)", - severity: [], - }], + vulnerabilities: [ + { + id: "MAL-2025-21003", + aliases: [], + summary: "Malicious code in fs (npm)", + severity: [], + }, + ], ...overrides, }); } @@ -1047,7 +1347,7 @@ describe("output formatters", () => { it("getRecommendedAction: direct malicious finding returns remove message", () => { const finding = createMaliciousFinding({ relationship: "direct" }); expect(getRecommendedAction(finding)).toBe( - "This package has a malicious code advisory. Remove it from your dependencies." + "This package has a malicious code advisory. Remove it from your dependencies.", ); }); @@ -1057,14 +1357,14 @@ describe("output formatters", () => { dependencyPaths: [["project", "parent", "lodash"]], }); expect(getRecommendedAction(finding)).toBe( - "This package has a malicious code advisory. Upgrade or remove the parent package that pulls it in." + "This package has a malicious code advisory. Upgrade or remove the parent package that pulls it in.", ); }); it("summarizeNextAction: direct malicious finding returns remove message", () => { const finding = createMaliciousFinding({ relationship: "direct" }); expect(summarizeNextAction(finding)).toBe( - "This package has a malicious code advisory. Remove it from your dependencies." + "This package has a malicious code advisory. Remove it from your dependencies.", ); }); @@ -1074,7 +1374,7 @@ describe("output formatters", () => { dependencyPaths: [["project", "parent", "lodash"]], }); expect(summarizeNextAction(finding)).toBe( - "This package has a malicious code advisory. Upgrade or remove the parent package that pulls it in." + "This package has a malicious code advisory. Upgrade or remove the parent package that pulls it in.", ); }); @@ -1084,19 +1384,26 @@ describe("output formatters", () => { firstFixedVersion: null, recommendedParentUpgrade: undefined, recommendedNpmTransitiveRemediation: undefined, - vulnerabilities: [{ - id: "GHSA-abc1-2345-6789", - aliases: [], - summary: "Some vulnerability", - severity: [], - }], + vulnerabilities: [ + { + id: "GHSA-abc1-2345-6789", + aliases: [], + summary: "Some vulnerability", + severity: [], + }, + ], }); expect(getRecommendedAction(finding)).toContain("Consider replacing"); }); it("getRecommendedAction returns unverifiable message for private registry MAL- finding", () => { const finding = createMaliciousFinding({ - pkg: { name: "evil-pkg", version: "1.0.0", ecosystem: "npm", resolvedUrl: "https://private.registry.com/evil-pkg-1.0.0.tgz" }, + pkg: { + name: "evil-pkg", + version: "1.0.0", + ecosystem: "npm", + resolvedUrl: "https://private.registry.com/evil-pkg-1.0.0.tgz", + }, maliciousUnverifiable: true, }); const action = getRecommendedAction(finding); @@ -1108,7 +1415,12 @@ describe("output formatters", () => { it("moves unpublishable fixed-version hints out of runnable commands", () => { const findings = [ createFinding({ - pkg: { name: "request", version: "2.88.2", ecosystem: "npm", paths: [["project", "request"]] }, + pkg: { + name: "request", + version: "2.88.2", + ecosystem: "npm", + paths: [["project", "request"]], + }, relationship: "direct", dependencyPaths: [["project", "request"]], severity: "low", @@ -1120,7 +1432,10 @@ describe("output formatters", () => { }), ]; - const plan = buildSuggestedFixCommandPlan(findings, createScanInputForSource("package-lock")); + const plan = buildSuggestedFixCommandPlan( + findings, + createScanInputForSource("package-lock"), + ); expect(plan?.sections).toEqual([]); expect(plan?.skipped).toEqual([ @@ -1148,7 +1463,12 @@ describe("output printers", () => { const findings = [ createFinding(), createFinding({ - pkg: { name: "minimist", version: "0.0.8", ecosystem: "npm", paths: [["project", "minimist"]] }, + pkg: { + name: "minimist", + version: "0.0.8", + ecosystem: "npm", + paths: [["project", "minimist"]], + }, relationship: "direct", dependencyPaths: [["project", "minimist"]], severity: "high", @@ -1163,10 +1483,16 @@ describe("output printers", () => { printActionSummary(findings); }); - expect(lines[0]).toContain("✗ Found 2 packages (2 CVEs) with known OSV matches from package-lock"); + expect(lines[0]).toContain( + "✗ Found 2 packages (2 CVEs) with known OSV matches from package-lock", + ); expect(lines.join("\n")).toContain("Quick take"); - expect(lines.join("\n")).toContain("1 vulnerable package look directly fixable in this project."); - expect(lines.join("\n")).toContain("1 issue come through other dependencies."); + expect(lines.join("\n")).toContain( + "1 vulnerable package look directly fixable in this project.", + ); + expect(lines.join("\n")).toContain( + "1 issue come through other dependencies.", + ); }); it("prints a table and final status for findings", () => { @@ -1177,11 +1503,15 @@ describe("output printers", () => { printFinalStatus(findings); }); - expect(lines.join("\n")).toContain("Showing high+ findings in the main table. Use --all to show everything."); + expect(lines.join("\n")).toContain( + "Showing high+ findings in the main table. Use --all to show everything.", + ); expect(lines.join("\n")).toContain("Package"); expect(lines.join("\n")).toContain("lodash"); expect(lines.join("\n")).toContain("OSV-123"); - expect(lines.join("\n")).toContain("✖ Scan complete. 1 issue found (1 critical, 0 high)."); + expect(lines.join("\n")).toContain( + "✖ Scan complete. 1 issue found (1 critical, 0 high).", + ); }); it("shows ⚠ no fix in the Fixed column when firstFixedVersion is null", () => { @@ -1202,12 +1532,14 @@ describe("output printers", () => { const finding = createFinding({ firstFixedVersion: null, recommendedParentUpgrade: undefined, - vulnerabilities: [{ - id: "MAL-2025-21003", - aliases: [], - summary: "Malicious code in fs (npm)", - severity: [], - }], + vulnerabilities: [ + { + id: "MAL-2025-21003", + aliases: [], + summary: "Malicious code in fs (npm)", + severity: [], + }, + ], }); const lines = captureLogs(() => { @@ -1221,16 +1553,23 @@ describe("output printers", () => { it("prints malicious package legend after table for MAL-* findings", () => { const direct = createFinding({ - pkg: { name: "fs", version: "0.0.1-security", ecosystem: "npm", paths: [["project", "fs"]] }, + pkg: { + name: "fs", + version: "0.0.1-security", + ecosystem: "npm", + paths: [["project", "fs"]], + }, relationship: "direct", firstFixedVersion: null, recommendedParentUpgrade: undefined, - vulnerabilities: [{ - id: "MAL-2025-21003", - aliases: [], - summary: "Malicious code in fs (npm)", - severity: [], - }], + vulnerabilities: [ + { + id: "MAL-2025-21003", + aliases: [], + summary: "Malicious code in fs (npm)", + severity: [], + }, + ], }); const lines = captureLogs(() => { @@ -1245,17 +1584,24 @@ describe("output printers", () => { it("prints transitive malicious package legend with parent upgrade message", () => { const transitive = createFinding({ - pkg: { name: "bad-pkg", version: "1.0.0", ecosystem: "npm", paths: [["project", "parent", "bad-pkg"]] }, + pkg: { + name: "bad-pkg", + version: "1.0.0", + ecosystem: "npm", + paths: [["project", "parent", "bad-pkg"]], + }, relationship: "transitive", dependencyPaths: [["project", "parent", "bad-pkg"]], firstFixedVersion: null, recommendedParentUpgrade: undefined, - vulnerabilities: [{ - id: "MAL-2024-99999", - aliases: [], - summary: "Malicious code in bad-pkg", - severity: [], - }], + vulnerabilities: [ + { + id: "MAL-2024-99999", + aliases: [], + summary: "Malicious code in bad-pkg", + severity: [], + }, + ], }); const lines = captureLogs(() => { @@ -1265,7 +1611,9 @@ describe("output printers", () => { const output = lines.join("\n"); expect(output).toContain("⚠ Malicious package advisory:"); expect(output).toContain("bad-pkg@1.0.0"); - expect(output).toContain("Upgrade or remove the parent package that pulls it in."); + expect(output).toContain( + "Upgrade or remove the parent package that pulls it in.", + ); }); it("does not print malicious legend when no malicious findings present", () => { @@ -1282,7 +1630,12 @@ describe("output printers", () => { it("prints suggested fix commands for verbose output", () => { const findings = [ createFinding({ - pkg: { name: "minimist", version: "0.0.8", ecosystem: "npm", paths: [["project", "minimist"]] }, + pkg: { + name: "minimist", + version: "0.0.8", + ecosystem: "npm", + paths: [["project", "minimist"]], + }, relationship: "direct", dependencyPaths: [["project", "minimist"]], severity: "critical", @@ -1290,7 +1643,12 @@ describe("output printers", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "tar", version: "6.2.1", ecosystem: "npm", paths: [["project", "tar"]] }, + pkg: { + name: "tar", + version: "6.2.1", + ecosystem: "npm", + paths: [["project", "tar"]], + }, relationship: "direct", dependencyPaths: [["project", "tar"]], severity: "medium", @@ -1298,7 +1656,12 @@ describe("output printers", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "marsdb", version: "0.6.11", ecosystem: "npm", paths: [["project", "marsdb"]] }, + pkg: { + name: "marsdb", + version: "0.6.11", + ecosystem: "npm", + paths: [["project", "marsdb"]], + }, relationship: "direct", dependencyPaths: [["project", "marsdb"]], severity: "critical", @@ -1308,12 +1671,19 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); }); expect(lines.join("\n")).toContain("Suggested Fix Commands"); - expect(lines.join("\n")).toContain("Detected package manager: npm (package-lock.json)"); - expect(lines.join("\n")).toContain("2 command groups ready across 2 packages"); + expect(lines.join("\n")).toContain( + "Detected package manager: npm (package-lock.json)", + ); + expect(lines.join("\n")).toContain( + "2 command groups ready across 2 packages", + ); expect(lines.join("\n")).toContain("Critical severity fix commands"); expect(lines.join("\n")).toContain("> npm install minimist@1.2.8"); expect(lines.join("\n")).toContain("npm install minimist@1.2.8"); @@ -1324,7 +1694,12 @@ describe("output printers", () => { it("prints a parent-upgrade table before the command callout when transitive targets are actionable", () => { const findings = [ createFinding({ - pkg: { name: "diff", version: "7.0.0", ecosystem: "npm", paths: [["project", "mocha", "diff"]] }, + pkg: { + name: "diff", + version: "7.0.0", + ecosystem: "npm", + paths: [["project", "mocha", "diff"]], + }, relationship: "transitive", dependencyPaths: [["project", "mocha", "diff"]], severity: "medium", @@ -1342,7 +1717,10 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); }); const output = lines.join("\n"); @@ -1355,13 +1733,20 @@ describe("output printers", () => { expect(output).toContain("11.7.5"); expect(output).toContain("12.0.0-beta-4"); expect(output).toContain("Parent upgrade for vulnerable diff@7.0.0"); - expect(output.indexOf("Context")).toBeLessThan(output.indexOf("> npm install mocha@12.0.0-beta-4")); + expect(output.indexOf("Context")).toBeLessThan( + output.indexOf("> npm install mocha@12.0.0-beta-4"), + ); }); it("prints npm update commands for in-range transitive remediation outcomes", () => { const findings = [ createFinding({ - pkg: { name: "diff", version: "5.0.0", ecosystem: "npm", paths: [["project", "mocha", "diff"]] }, + pkg: { + name: "diff", + version: "5.0.0", + ecosystem: "npm", + paths: [["project", "mocha", "diff"]], + }, relationship: "transitive", dependencyPaths: [["project", "mocha", "diff"]], severity: "high", @@ -1373,13 +1758,17 @@ describe("output printers", () => { currentVersion: "10.0.0", targetChildVersion: "5.1.0", viaPath: ["project", "mocha", "diff"], - reason: "mocha@10.0.0 already allows diff@5.1.0 within the current dependency range", + reason: + "mocha@10.0.0 already allows diff@5.1.0 within the current dependency range", }, }), ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); printCompactOutput(findings, createScanInputForSource("package-lock")); }); const output = lines.join("\n"); @@ -1395,7 +1784,12 @@ describe("output printers", () => { it("prints registry-adjusted notes before the adjusted command", () => { const findings = [ createFinding({ - pkg: { name: "serialize-javascript", version: "7.0.3", ecosystem: "npm", paths: [["project", "serialize-javascript"]] }, + pkg: { + name: "serialize-javascript", + version: "7.0.3", + ecosystem: "npm", + paths: [["project", "serialize-javascript"]], + }, relationship: "direct", dependencyPaths: [["project", "serialize-javascript"]], severity: "low", @@ -1405,7 +1799,12 @@ describe("output printers", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "lodash.template", version: "3.6.2", ecosystem: "npm", paths: [["project", "lodash.template"]] }, + pkg: { + name: "lodash.template", + version: "3.6.2", + ecosystem: "npm", + paths: [["project", "lodash.template"]], + }, relationship: "direct", dependencyPaths: [["project", "lodash.template"]], severity: "low", @@ -1418,7 +1817,10 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); }); const output = lines.join("\n"); @@ -1437,19 +1839,30 @@ describe("output printers", () => { "Note: Advisory fixed-version hint 4.17.21 is not published on npm for lodash.template; using nearest published version 4.18.0.", ); expect(output).toContain("> npm install lodash.template@4.18.0"); - expect(output.indexOf("Low severity direct fixes")).toBeLessThan(output.indexOf("Low severity direct fixes (registry-adjusted)")); - expect(output.indexOf("lodash.template")).toBeLessThan( - output.indexOf("Note: Advisory fixed-version hint 4.17.21 is not published on npm for lodash.template; using nearest published version 4.18.0."), + expect(output.indexOf("Low severity direct fixes")).toBeLessThan( + output.indexOf("Low severity direct fixes (registry-adjusted)"), ); - expect(output.indexOf("Note: Advisory fixed-version hint 4.17.21 is not published on npm for lodash.template; using nearest published version 4.18.0.")).toBeLessThan( - output.indexOf("> npm install lodash.template@4.18.0"), + expect(output.indexOf("lodash.template")).toBeLessThan( + output.indexOf( + "Note: Advisory fixed-version hint 4.17.21 is not published on npm for lodash.template; using nearest published version 4.18.0.", + ), ); + expect( + output.indexOf( + "Note: Advisory fixed-version hint 4.17.21 is not published on npm for lodash.template; using nearest published version 4.18.0.", + ), + ).toBeLessThan(output.indexOf("> npm install lodash.template@4.18.0")); }); it("prints a validation summary for adjusted targets that include candidate-evaluation counts", () => { const findings = [ createFinding({ - pkg: { name: "diff", version: "3.5.1", ecosystem: "npm", paths: [["project", "diff"]] }, + pkg: { + name: "diff", + version: "3.5.1", + ecosystem: "npm", + paths: [["project", "diff"]], + }, relationship: "direct", dependencyPaths: [["project", "diff"]], severity: "medium", @@ -1462,7 +1875,12 @@ describe("output printers", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "tar", version: "7.5.3", ecosystem: "npm", paths: [["project", "tar"]] }, + pkg: { + name: "tar", + version: "7.5.3", + ecosystem: "npm", + paths: [["project", "tar"]], + }, relationship: "direct", dependencyPaths: [["project", "tar"]], severity: "medium", @@ -1477,7 +1895,10 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); }); const output = lines.join("\n"); @@ -1499,14 +1920,23 @@ describe("output printers", () => { expect(output).toContain("Total"); expect(output).toContain("24"); expect(output).toContain("22"); - expect(output).not.toContain("Note: Advisory fixed-version hint 3.5.1 is still known vulnerable for diff;"); - expect(output).not.toContain("Note: Advisory fixed-version hint 7.5.3 is still known vulnerable for tar;"); + expect(output).not.toContain( + "Note: Advisory fixed-version hint 3.5.1 is still known vulnerable for diff;", + ); + expect(output).not.toContain( + "Note: Advisory fixed-version hint 7.5.3 is still known vulnerable for tar;", + ); }); it("shows Breaking? column with ⚠ for major-version bumps and blank for patch bumps", () => { const findings = [ createFinding({ - pkg: { name: "jsonwebtoken", version: "8.5.1", ecosystem: "npm", paths: [["project", "jsonwebtoken"]] }, + pkg: { + name: "jsonwebtoken", + version: "8.5.1", + ecosystem: "npm", + paths: [["project", "jsonwebtoken"]], + }, relationship: "direct", dependencyPaths: [["project", "jsonwebtoken"]], severity: "high", @@ -1518,7 +1948,12 @@ describe("output printers", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "fastify", version: "5.8.4", ecosystem: "npm", paths: [["project", "fastify"]] }, + pkg: { + name: "fastify", + version: "5.8.4", + ecosystem: "npm", + paths: [["project", "fastify"]], + }, relationship: "direct", dependencyPaths: [["project", "fastify"]], severity: "medium", @@ -1532,7 +1967,10 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); }); const output = lines.join("\n"); @@ -1546,7 +1984,7 @@ describe("output printers", () => { // Patch bump (fastify 5.8.4→5.8.5) does not trigger the icon expect(output).not.toContain("5.8.5 (breaking change)"); // Column alignment: each data row should have 6 pipe-delimited cells - const tableRows = lines.filter(l => l.startsWith("│")); + const tableRows = lines.filter((l) => l.startsWith("│")); for (const row of tableRows) { expect((row.match(/│/g) ?? []).length).toBe(8); // 7 cells = 8 pipes } @@ -1559,7 +1997,12 @@ describe("output printers", () => { // Package/Current/Target/Versions scanned/Still known vulnerable/Breaking?. const findings = [ createFinding({ - pkg: { name: "jsonwebtoken", version: "8.5.1", ecosystem: "npm", paths: [["project", "jsonwebtoken"]] }, + pkg: { + name: "jsonwebtoken", + version: "8.5.1", + ecosystem: "npm", + paths: [["project", "jsonwebtoken"]], + }, relationship: "direct", dependencyPaths: [["project", "jsonwebtoken"]], severity: "high", @@ -1573,7 +2016,10 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); }); const output = lines.join("\n"); @@ -1584,7 +2030,9 @@ describe("output printers", () => { expect(output).toContain("jsonwebtoken"); expect(output).toContain("9.0.0"); // Table must appear before the command callout - expect(output.indexOf("Target")).toBeLessThan(output.indexOf("> npm install jsonwebtoken@9.0.0")); + expect(output.indexOf("Target")).toBeLessThan( + output.indexOf("> npm install jsonwebtoken@9.0.0"), + ); }); it("excludes transitive skips from the no-auto-fix section and only shows direct ones", () => { @@ -1593,7 +2041,12 @@ describe("output printers", () => { // Only direct deps with no confident fix command should appear there. const findings = [ createFinding({ - pkg: { name: "marsdb", version: "0.6.11", ecosystem: "npm", paths: [["project", "marsdb"]] }, + pkg: { + name: "marsdb", + version: "0.6.11", + ecosystem: "npm", + paths: [["project", "marsdb"]], + }, relationship: "direct", dependencyPaths: [["project", "marsdb"]], severity: "critical", @@ -1601,7 +2054,12 @@ describe("output printers", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "braces", version: "2.3.2", ecosystem: "npm", paths: [["project", "check-dependencies", "braces"]] }, + pkg: { + name: "braces", + version: "2.3.2", + ecosystem: "npm", + paths: [["project", "check-dependencies", "braces"]], + }, relationship: "transitive", dependencyPaths: [["project", "check-dependencies", "braces"]], severity: "high", @@ -1611,11 +2069,16 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommandSkips(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommandSkips( + findings, + createScanInputForSource("package-lock"), + ); }); const output = lines.join("\n"); - expect(output).toContain("No auto-fix command available for these direct dependencies:"); + expect(output).toContain( + "No auto-fix command available for these direct dependencies:", + ); expect(output).toContain("marsdb@0.6.11"); // transitive must not appear in this section expect(output).not.toContain("braces@2.3.2"); @@ -1624,7 +2087,12 @@ describe("output printers", () => { it("prints unpublishable fixed-version hints separately from runnable commands", () => { const findings = [ createFinding({ - pkg: { name: "request", version: "2.88.2", ecosystem: "npm", paths: [["project", "request"]] }, + pkg: { + name: "request", + version: "2.88.2", + ecosystem: "npm", + paths: [["project", "request"]], + }, relationship: "direct", dependencyPaths: [["project", "request"]], severity: "low", @@ -1637,8 +2105,14 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); - printSuggestedFixCommandSkips(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); + printSuggestedFixCommandSkips( + findings, + createScanInputForSource("package-lock"), + ); }); const output = lines.join("\n"); @@ -1687,10 +2161,16 @@ describe("output printers", () => { printTable(findings, null); }); - expect(linesWithThreshold.join("\n")).toContain("Showing medium+ findings in the main table. Use --all to show everything."); - expect(linesWithThreshold.join("\n")).toContain("Tip: use --all to include low findings"); + expect(linesWithThreshold.join("\n")).toContain( + "Showing medium+ findings in the main table. Use --all to show everything.", + ); + expect(linesWithThreshold.join("\n")).toContain( + "Tip: use --all to include low findings", + ); expect(linesWithNull.join("\n")).not.toContain("Showing medium+ findings"); - expect(linesWithNull.join("\n")).not.toContain("Tip: use --all to include low findings"); + expect(linesWithNull.join("\n")).not.toContain( + "Tip: use --all to include low findings", + ); }); it("shows the full findings table in compact output when all option is true", () => { @@ -1704,7 +2184,9 @@ describe("output printers", () => { ]; const linesAll = captureLogs(() => { - printCompactOutput(findings, createScanInputForSource("package-lock"), { all: true }); + printCompactOutput(findings, createScanInputForSource("package-lock"), { + all: true, + }); }); const linesDefault = captureLogs(() => { printCompactOutput(findings, createScanInputForSource("package-lock")); @@ -1727,14 +2209,26 @@ describe("output printers", () => { it("shows malicious advisory inline hint and legend in compact output", () => { const finding = createFinding({ - pkg: { name: "fs", version: "0.0.1-security", ecosystem: "npm", paths: [["project", "fs"]] }, + pkg: { + name: "fs", + version: "0.0.1-security", + ecosystem: "npm", + paths: [["project", "fs"]], + }, severity: "unknown", relationship: "direct", dependencyPaths: [["project", "fs"]], firstFixedVersion: null, recommendedParentUpgrade: undefined, recommendedNpmTransitiveRemediation: undefined, - vulnerabilities: [{ id: "MAL-2025-21003", aliases: [], summary: "Malicious code in fs (npm)", severity: [] }], + vulnerabilities: [ + { + id: "MAL-2025-21003", + aliases: [], + summary: "Malicious code in fs (npm)", + severity: [], + }, + ], }); const lines = captureLogs(() => { printCompactOutput([finding], createScanInputForSource("package-lock")); @@ -1742,56 +2236,98 @@ describe("output printers", () => { const output = lines.join("\n"); expect(output).toContain("fs"); expect(output).toContain("UNKNOWN"); - expect(output).toContain("⚠ Malicious: Remove this package from your dependencies immediately."); + expect(output).toContain( + "⚠ Malicious: Remove this package from your dependencies immediately.", + ); expect(output).toContain("⚠ Malicious package advisory:"); - expect(output).toContain("fs@0.0.1-security - Remove it from your dependencies immediately."); + expect(output).toContain( + "fs@0.0.1-security - Remove it from your dependencies immediately.", + ); }); it("shows unverifiable inline hint and legend in compact output for private registry MAL- finding", () => { const finding = createFinding({ - pkg: { name: "node-ipc", version: "9.2.3", ecosystem: "npm", paths: [["project", "node-ipc"]], resolvedUrl: "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz" }, + pkg: { + name: "node-ipc", + version: "9.2.3", + ecosystem: "npm", + paths: [["project", "node-ipc"]], + resolvedUrl: + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + }, severity: "unknown", relationship: "direct", dependencyPaths: [["project", "node-ipc"]], firstFixedVersion: null, recommendedParentUpgrade: undefined, recommendedNpmTransitiveRemediation: undefined, - vulnerabilities: [{ id: "MAL-2026-3744", aliases: [], summary: "Malicious code in node-ipc", severity: [] }], + vulnerabilities: [ + { + id: "MAL-2026-3744", + aliases: [], + summary: "Malicious code in node-ipc", + severity: [], + }, + ], maliciousUnverifiable: true, }); const lines = captureLogs(() => { printCompactOutput([finding], createScanInputForSource("package-lock")); }); const output = lines.join("\n"); - expect(output).toContain("⚠ Unverifiable (private source) - MAL- advisory could not be confirmed for this artifact."); - expect(output).toContain("node-ipc@9.2.3 - Unverifiable (private source) - verify artifact source manually"); + expect(output).toContain( + "⚠ Unverifiable (private source) - MAL- advisory could not be confirmed for this artifact.", + ); + expect(output).toContain( + "node-ipc@9.2.3 - Unverifiable (private source) - verify artifact source manually", + ); expect(output).not.toContain("⚠ Malicious: Remove"); - expect(output).not.toContain("Remove it from your dependencies immediately."); + expect(output).not.toContain( + "Remove it from your dependencies immediately.", + ); }); it("does not show malicious legend in compact output when --all is set (printTable shows it instead)", () => { const finding = createFinding({ - pkg: { name: "fs", version: "0.0.1-security", ecosystem: "npm", paths: [["project", "fs"]] }, + pkg: { + name: "fs", + version: "0.0.1-security", + ecosystem: "npm", + paths: [["project", "fs"]], + }, severity: "unknown", relationship: "direct", dependencyPaths: [["project", "fs"]], firstFixedVersion: null, recommendedParentUpgrade: undefined, recommendedNpmTransitiveRemediation: undefined, - vulnerabilities: [{ id: "MAL-2025-21003", aliases: [], summary: "Malicious code in fs (npm)", severity: [] }], + vulnerabilities: [ + { + id: "MAL-2025-21003", + aliases: [], + summary: "Malicious code in fs (npm)", + severity: [], + }, + ], }); const lines = captureLogs(() => { - printCompactOutput([finding], createScanInputForSource("package-lock"), { all: true }); + printCompactOutput([finding], createScanInputForSource("package-lock"), { + all: true, + }); }); const output = lines.join("\n"); // legend appears exactly once — from printTable, not from the compact legend block - const legendCount = (output.match(/⚠ Malicious package advisory:/g) ?? []).length; + const legendCount = (output.match(/⚠ Malicious package advisory:/g) ?? []) + .length; expect(legendCount).toBe(1); }); it("prints compact output for urgent findings and a clean final line for empty scans", () => { const linesWithFinding = captureLogs(() => { - printCompactOutput([createFinding()], createScanInputForSource("package-lock")); + printCompactOutput( + [createFinding()], + createScanInputForSource("package-lock"), + ); }); const emptyLines = captureLogs(() => { printCompactOutput([], createScanInputForSource("package-lock")); @@ -1799,41 +2335,72 @@ describe("output printers", () => { expect(linesWithFinding.join("\n")).toContain("📦 Vulnerabilities found"); expect(linesWithFinding.join("\n")).toContain("🛠 Suggested Fix Commands"); - expect(linesWithFinding.join("\n")).toContain("1 command group ready across 1 package"); + expect(linesWithFinding.join("\n")).toContain( + "1 command group ready across 1 package", + ); expect(linesWithFinding.join("\n")).toContain("> npm install app@1.1.0"); expect(linesWithFinding.join("\n")).toContain("npm install app@1.1.0"); - expect(emptyLines).toContain("✔ Scan complete. No known vulnerabilities found."); + expect(emptyLines).toContain( + "✔ Scan complete. No known vulnerabilities found.", + ); }); it("keeps direct unknown-severity findings visible in compact output even when urgent slots are full", () => { const findings = [ createFinding({ - pkg: { name: "critical-a", version: "1.0.0", ecosystem: "npm", paths: [["project", "critical-a"]] }, + pkg: { + name: "critical-a", + version: "1.0.0", + ecosystem: "npm", + paths: [["project", "critical-a"]], + }, severity: "critical", relationship: "direct", dependencyPaths: [["project", "critical-a"]], }), createFinding({ - pkg: { name: "critical-b", version: "1.0.0", ecosystem: "npm", paths: [["project", "critical-b"]] }, + pkg: { + name: "critical-b", + version: "1.0.0", + ecosystem: "npm", + paths: [["project", "critical-b"]], + }, severity: "critical", relationship: "direct", dependencyPaths: [["project", "critical-b"]], }), createFinding({ - pkg: { name: "high-c", version: "1.0.0", ecosystem: "npm", paths: [["project", "high-c"]] }, + pkg: { + name: "high-c", + version: "1.0.0", + ecosystem: "npm", + paths: [["project", "high-c"]], + }, severity: "high", relationship: "direct", dependencyPaths: [["project", "high-c"]], }), createFinding({ - pkg: { name: "fs", version: "0.0.1-security", ecosystem: "npm", paths: [["project", "fs"]] }, + pkg: { + name: "fs", + version: "0.0.1-security", + ecosystem: "npm", + paths: [["project", "fs"]], + }, severity: "unknown", relationship: "direct", dependencyPaths: [["project", "fs"]], firstFixedVersion: null, recommendedParentUpgrade: undefined, recommendedNpmTransitiveRemediation: undefined, - vulnerabilities: [{ id: "MAL-2025-21003", aliases: [], summary: "Malicious code in fs (npm)", severity: [] }], + vulnerabilities: [ + { + id: "MAL-2025-21003", + aliases: [], + summary: "Malicious code in fs (npm)", + severity: [], + }, + ], }), ]; @@ -1846,13 +2413,20 @@ describe("output printers", () => { expect(output).toContain("critical-b@1.0.0"); expect(output).toContain("high-c@1.0.0"); expect(output).toContain("fs@0.0.1-security"); - expect(output).toContain("⚠ Malicious: Remove this package from your dependencies immediately."); + expect(output).toContain( + "⚠ Malicious: Remove this package from your dependencies immediately.", + ); }); it("renders Context column for parent-upgrade targets in urgent sections", () => { const findings = [ createFinding({ - pkg: { name: "minimist", version: "0.0.8", ecosystem: "npm", paths: [["project", "minimist"]] }, + pkg: { + name: "minimist", + version: "0.0.8", + ecosystem: "npm", + paths: [["project", "minimist"]], + }, relationship: "direct", dependencyPaths: [["project", "minimist"]], severity: "critical", @@ -1860,7 +2434,12 @@ describe("output printers", () => { recommendedParentUpgrade: undefined, }), createFinding({ - pkg: { name: "lodash", version: "4.17.20", ecosystem: "npm", paths: [["project", "app", "lodash"]] }, + pkg: { + name: "lodash", + version: "4.17.20", + ecosystem: "npm", + paths: [["project", "app", "lodash"]], + }, relationship: "transitive", dependencyPaths: [["project", "app", "lodash"]], severity: "critical", @@ -1878,7 +2457,10 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); }); const output = lines.join("\n"); @@ -1890,7 +2472,12 @@ describe("output printers", () => { it("renders Context column for urgent sections containing only parent-upgrade targets", () => { const findings = [ createFinding({ - pkg: { name: "lodash", version: "4.17.20", ecosystem: "npm", paths: [["project", "app", "lodash"]] }, + pkg: { + name: "lodash", + version: "4.17.20", + ecosystem: "npm", + paths: [["project", "app", "lodash"]], + }, relationship: "transitive", dependencyPaths: [["project", "app", "lodash"]], severity: "critical", @@ -1908,7 +2495,10 @@ describe("output printers", () => { ]; const lines = captureLogs(() => { - printSuggestedFixCommands(findings, createScanInputForSource("package-lock")); + printSuggestedFixCommands( + findings, + createScanInputForSource("package-lock"), + ); }); const output = lines.join("\n"); @@ -1948,7 +2538,10 @@ describe("output printers", () => { }, }); - const plan = buildSuggestedFixCommandPlan([finding], createScanInputForSource("pnpm-lock")); + const plan = buildSuggestedFixCommandPlan( + [finding], + createScanInputForSource("pnpm-lock"), + ); expect(plan?.targets[0]).toEqual( expect.objectContaining({ @@ -1961,13 +2554,22 @@ describe("output printers", () => { ); const lines = captureLogs(() => { - printSuggestedFixCommands([finding], createScanInputForSource("pnpm-lock")); + printSuggestedFixCommands( + [finding], + createScanInputForSource("pnpm-lock"), + ); }); const output = lines.join("\n"); - expect(output).toContain("Path-specific parent upgrade for project -> lint-staged ->"); - expect(output).toContain("picomatch (picomatch@4.0.3); run this command, then rescan."); - expect(output).toContain("1 other known path may still need separate parent upgrades."); + expect(output).toContain( + "Path-specific parent upgrade for project -> lint-staged ->", + ); + expect(output).toContain( + "picomatch (picomatch@4.0.3); run this command, then rescan.", + ); + expect(output).toContain( + "1 other known path may still need separate parent upgrades.", + ); expect(output).not.toContain("…"); }); @@ -1976,7 +2578,12 @@ describe("output printers", () => { printCompactOutput( [ createFinding({ - pkg: { name: "tar", version: "7.5.3", ecosystem: "npm", paths: [["project", "tar"]] }, + pkg: { + name: "tar", + version: "7.5.3", + ecosystem: "npm", + paths: [["project", "tar"]], + }, relationship: "direct", dependencyPaths: [["project", "tar"]], severity: "medium", @@ -1994,7 +2601,9 @@ describe("output printers", () => { }); const output = lines.join("\n"); - expect(output).toContain("Validation: scanned 22 package versions; 21 are still known vulnerable."); + expect(output).toContain( + "Validation: scanned 22 package versions; 21 are still known vulnerable.", + ); }); }); @@ -2004,8 +2613,18 @@ describe("printSummary CVE count", () => { const findings = [ createFinding({ vulnerabilities: [ - { id: "OSV-001", aliases: ["CVE-2026-0001"], summary: "A", severity: [] }, - { id: "OSV-002", aliases: ["CVE-2026-0002"], summary: "B", severity: [] }, + { + id: "OSV-001", + aliases: ["CVE-2026-0001"], + summary: "A", + severity: [], + }, + { + id: "OSV-002", + aliases: ["CVE-2026-0002"], + summary: "B", + severity: [], + }, ], }), createFinding({ @@ -2016,7 +2635,9 @@ describe("printSummary CVE count", () => { }), ]; printSummary(findings, 100, createScanInput()); - const allOutput = consoleSpy.mock.calls.map(c => stripAnsi(String(c[0]))).join("\n"); + const allOutput = consoleSpy.mock.calls + .map((c) => stripAnsi(String(c[0]))) + .join("\n"); expect(allOutput).toContain("2 packages (3 CVEs)"); consoleSpy.mockRestore(); }); @@ -2025,7 +2646,9 @@ describe("printSummary CVE count", () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const findings = [createFinding()]; // 1 package, 1 CVE (OSV-123) printSummary(findings, 100, createScanInput()); - const allOutput = consoleSpy.mock.calls.map(c => stripAnsi(String(c[0]))).join("\n"); + const allOutput = consoleSpy.mock.calls + .map((c) => stripAnsi(String(c[0]))) + .join("\n"); expect(allOutput).toContain("1 package (1 CVE)"); consoleSpy.mockRestore(); }); @@ -2043,7 +2666,9 @@ describe("printActionSummary CVE count", () => { }), ]; printActionSummary(findings); - const allOutput = consoleSpy.mock.calls.map(c => stripAnsi(String(c[0]))).join("\n"); + const allOutput = consoleSpy.mock.calls + .map((c) => stripAnsi(String(c[0]))) + .join("\n"); expect(allOutput).toContain("2 CVEs matched overall"); expect(allOutput).not.toContain("unique advisories"); consoleSpy.mockRestore(); @@ -2068,7 +2693,9 @@ describe("printCompactOutput CVE count", () => { }), ]; printCompactOutput(findings, createScanInput(), {}); - const allOutput = consoleSpy.mock.calls.map(c => stripAnsi(String(c[0]))).join("\n"); + const allOutput = consoleSpy.mock.calls + .map((c) => stripAnsi(String(c[0]))) + .join("\n"); expect(allOutput).toContain("2 packages"); expect(allOutput).toContain("3 CVEs"); expect(allOutput).not.toContain("vulnerable packages"); @@ -2089,15 +2716,30 @@ describe("countUniqueAdvisories", () => { it("counts multiple advisories on one finding", () => { const f = createFinding({ vulnerabilities: [ - { id: "OSV-001", aliases: ["CVE-2026-0001"], summary: "A", severity: [] }, - { id: "OSV-002", aliases: ["CVE-2026-0002"], summary: "B", severity: [] }, + { + id: "OSV-001", + aliases: ["CVE-2026-0001"], + summary: "A", + severity: [], + }, + { + id: "OSV-002", + aliases: ["CVE-2026-0002"], + summary: "B", + severity: [], + }, ], }); expect(countUniqueAdvisories([f])).toBe(2); }); it("deduplicates the same advisory ID appearing in multiple findings", () => { - const sharedVuln = { id: "OSV-001", aliases: [], summary: "X", severity: [] }; + const sharedVuln = { + id: "OSV-001", + aliases: [], + summary: "X", + severity: [], + }; const f1 = createFinding({ vulnerabilities: [sharedVuln] }); const f2 = createFinding({ pkg: { name: "express", version: "4.0.0", ecosystem: "npm" }, @@ -2128,14 +2770,20 @@ describe("createSpinner", () => { beforeEach(() => { isTTYDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY"); - Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true }); + Object.defineProperty(process.stdout, "isTTY", { + value: true, + configurable: true, + }); }); afterEach(() => { if (isTTYDescriptor) { Object.defineProperty(process.stdout, "isTTY", isTTYDescriptor); } else { - Object.defineProperty(process.stdout, "isTTY", { value: undefined, configurable: true }); + Object.defineProperty(process.stdout, "isTTY", { + value: undefined, + configurable: true, + }); } }); @@ -2145,7 +2793,7 @@ describe("createSpinner", () => { spinner.stop(); spinner.succeed("Done"); }); - expect(logs.some(l => l.includes("Done"))).toBe(true); + expect(logs.some((l) => l.includes("Done"))).toBe(true); }); it("succeed() is suppressed when json: true even in a TTY", () => { @@ -2169,32 +2817,62 @@ import { isGitSource, hasCommitShaPinning } from "../src/utils/advisory.js"; describe("isGitSource", () => { it("returns true for GitHub codeload URL", () => { - const pkg = { name: "pkg", version: "1.0.0", ecosystem: "npm", resolvedUrl: "https://codeload.github.com/org/repo/tar.gz/abc123" }; + const pkg = { + name: "pkg", + version: "1.0.0", + ecosystem: "npm", + resolvedUrl: "https://codeload.github.com/org/repo/tar.gz/abc123", + }; expect(isGitSource(pkg)).toBe(true); }); it("returns true for github.com URL", () => { - const pkg = { name: "pkg", version: "1.0.0", ecosystem: "npm", resolvedUrl: "https://github.com/org/repo/archive/abc123.tar.gz" }; + const pkg = { + name: "pkg", + version: "1.0.0", + ecosystem: "npm", + resolvedUrl: "https://github.com/org/repo/archive/abc123.tar.gz", + }; expect(isGitSource(pkg)).toBe(true); }); it("returns true for gitlab.com URL", () => { - const pkg = { name: "pkg", version: "1.0.0", ecosystem: "npm", resolvedUrl: "https://gitlab.com/org/repo/-/archive/abc123/repo.tar.gz" }; + const pkg = { + name: "pkg", + version: "1.0.0", + ecosystem: "npm", + resolvedUrl: "https://gitlab.com/org/repo/-/archive/abc123/repo.tar.gz", + }; expect(isGitSource(pkg)).toBe(true); }); it("returns true for git+https protocol", () => { - const pkg = { name: "pkg", version: "1.0.0", ecosystem: "npm", resolvedUrl: "git+https://github.com/org/repo.git" }; + const pkg = { + name: "pkg", + version: "1.0.0", + ecosystem: "npm", + resolvedUrl: "git+https://github.com/org/repo.git", + }; expect(isGitSource(pkg)).toBe(true); }); it("returns false for npm registry URL", () => { - const pkg = { name: "pkg", version: "1.0.0", ecosystem: "npm", resolvedUrl: "https://registry.npmjs.org/pkg/-/pkg-1.0.0.tgz" }; + const pkg = { + name: "pkg", + version: "1.0.0", + ecosystem: "npm", + resolvedUrl: "https://registry.npmjs.org/pkg/-/pkg-1.0.0.tgz", + }; expect(isGitSource(pkg)).toBe(false); }); it("returns false for private npm registry URL", () => { - const pkg = { name: "pkg", version: "1.0.0", ecosystem: "npm", resolvedUrl: "https://npm.internal.example.com/pkg/-/pkg-1.0.0.tgz" }; + const pkg = { + name: "pkg", + version: "1.0.0", + ecosystem: "npm", + resolvedUrl: "https://npm.internal.example.com/pkg/-/pkg-1.0.0.tgz", + }; expect(isGitSource(pkg)).toBe(false); }); @@ -2206,12 +2884,23 @@ describe("isGitSource", () => { describe("hasCommitShaPinning", () => { it("returns true when URL contains a 40-char hex SHA", () => { - const pkg = { name: "pkg", version: "1.0.0", ecosystem: "npm", resolvedUrl: "https://codeload.github.com/org/repo/tar.gz/9af9b3c49515b85598cd88de3e8cc20c7a98efbb" }; + const pkg = { + name: "pkg", + version: "1.0.0", + ecosystem: "npm", + resolvedUrl: + "https://codeload.github.com/org/repo/tar.gz/9af9b3c49515b85598cd88de3e8cc20c7a98efbb", + }; expect(hasCommitShaPinning(pkg)).toBe(true); }); it("returns false when URL has no SHA (short ref or tag)", () => { - const pkg = { name: "pkg", version: "1.0.0", ecosystem: "npm", resolvedUrl: "https://github.com/org/repo/archive/main.tar.gz" }; + const pkg = { + name: "pkg", + version: "1.0.0", + ecosystem: "npm", + resolvedUrl: "https://github.com/org/repo/archive/main.tar.gz", + }; expect(hasCommitShaPinning(pkg)).toBe(false); }); @@ -2224,8 +2913,21 @@ describe("hasCommitShaPinning", () => { describe("serializeFinding - git source MAL", () => { it("includes maliciousGitSource:true when finding has maliciousGitSource set", () => { const finding = createFinding({ - pkg: { name: "node-ipc", version: "9.2.3", ecosystem: "npm", resolvedUrl: "https://codeload.github.com/org/repo/tar.gz/9af9b3c49515b85598cd88de3e8cc20c7a98efbb" }, - vulnerabilities: [{ id: "MAL-2022-1000", summary: "Malicious package", aliases: [], severity: [] }], + pkg: { + name: "node-ipc", + version: "9.2.3", + ecosystem: "npm", + resolvedUrl: + "https://codeload.github.com/org/repo/tar.gz/9af9b3c49515b85598cd88de3e8cc20c7a98efbb", + }, + vulnerabilities: [ + { + id: "MAL-2022-1000", + summary: "Malicious package", + aliases: [], + severity: [], + }, + ], }); finding.maliciousGitSource = true; finding.maliciousGitSourcePinned = true; @@ -2246,22 +2948,39 @@ describe("serializeFinding - git source MAL", () => { describe("formatRelLabel", () => { it("returns 'direct' for a prod direct finding", () => { - const finding = { relationship: "direct", pkg: { name: "axios", version: "0.21.1", ecosystem: "npm", dev: false } }; + const finding = { + relationship: "direct", + pkg: { name: "axios", version: "0.21.1", ecosystem: "npm", dev: false }, + }; expect(formatRelLabel(finding)).toBe("direct"); }); it("returns 'direct · dev' for a dev direct finding", () => { - const finding = { relationship: "direct", pkg: { name: "jest", version: "29.0.0", ecosystem: "npm", dev: true } }; + const finding = { + relationship: "direct", + pkg: { name: "jest", version: "29.0.0", ecosystem: "npm", dev: true }, + }; expect(formatRelLabel(finding)).toBe("direct · dev"); }); it("returns 'transitive · dev' for a dev transitive finding", () => { - const finding = { relationship: "transitive", pkg: { name: "jest-runner", version: "29.0.0", ecosystem: "npm", dev: true } }; + const finding = { + relationship: "transitive", + pkg: { + name: "jest-runner", + version: "29.0.0", + ecosystem: "npm", + dev: true, + }, + }; expect(formatRelLabel(finding)).toBe("transitive · dev"); }); it("returns 'transitive' when dev is undefined", () => { - const finding = { relationship: "transitive", pkg: { name: "axios", version: "0.21.1", ecosystem: "npm" } }; + const finding = { + relationship: "transitive", + pkg: { name: "axios", version: "0.21.1", ecosystem: "npm" }, + }; expect(formatRelLabel(finding)).toBe("transitive"); }); }); diff --git a/tests/parent-upgrade.test.ts b/tests/parent-upgrade.test.ts index 3b094608..8a8731f3 100644 --- a/tests/parent-upgrade.test.ts +++ b/tests/parent-upgrade.test.ts @@ -1,8 +1,14 @@ import { jest } from "@jest/globals"; import type { Finding, PackageRef } from "../src/types.js"; import { clearPackumentCache } from "../src/remediation/npm-registry.js"; -import { createNpmTransitiveGraph, findSafeVersionWithinParentRange } from "../src/remediation/npm-transitive-graph.js"; -import { resolveNpmTransitiveRemediation, resolveTransitiveRemediationViaRegistry } from "../src/remediation/npm-transitive-resolution.js"; +import { + createNpmTransitiveGraph, + findSafeVersionWithinParentRange, +} from "../src/remediation/npm-transitive-graph.js"; +import { + resolveNpmTransitiveRemediation, + resolveTransitiveRemediationViaRegistry, +} from "../src/remediation/npm-transitive-resolution.js"; const fetchMock = jest.fn(); global.fetch = fetchMock as unknown as typeof fetch; @@ -81,7 +87,9 @@ function mockPackumentsByPackage(packuments: Record) { } async function loadResolver() { - const module = await import(`../src/remediation/parent-upgrade.js?test=${Date.now()}-${Math.random()}`); + const module = await import( + `../src/remediation/parent-upgrade.js?test=${Date.now()}-${Math.random()}` + ); return module.resolveRecommendedParentUpgrade; } @@ -529,8 +537,16 @@ describe("resolveNpmTransitiveRemediation — 3-level within-range gap (#522)", it("returns null for 3-level chains even when the immediate parent range already covers the fix (known bug #522)", async () => { const graph = createNpmTransitiveGraph({ nodes: [ - { id: "node_modules/aws-amplify", name: "aws-amplify", version: "6.16.3" }, - { id: "node_modules/@aws-amplify/core", name: "@aws-amplify/core", version: "6.16.1" }, + { + id: "node_modules/aws-amplify", + name: "aws-amplify", + version: "6.16.3", + }, + { + id: "node_modules/@aws-amplify/core", + name: "@aws-amplify/core", + version: "6.16.1", + }, { id: "node_modules/js-cookie", name: "js-cookie", version: "3.0.6" }, ], edges: [ @@ -566,14 +582,26 @@ describe("resolveNpmTransitiveRemediation — 3-level within-range gap (#522)", vulnerabilities: [{ id: "GHSA-qjx8-664m-686j" }], severity: "high", cveAliases: [], - dependencyPaths: [["project", "aws-amplify", "@aws-amplify/core", "js-cookie"]], + dependencyPaths: [ + ["project", "aws-amplify", "@aws-amplify/core", "js-cookie"], + ], relationship: "transitive", firstFixedVersion: "3.0.7", }, graph, packages: [ - { name: "aws-amplify", version: "6.16.3", ecosystem: "npm", paths: [["project", "aws-amplify"]] }, - { name: "@aws-amplify/core", version: "6.16.1", ecosystem: "npm", paths: [["project", "aws-amplify", "@aws-amplify/core"]] }, + { + name: "aws-amplify", + version: "6.16.3", + ecosystem: "npm", + paths: [["project", "aws-amplify"]], + }, + { + name: "@aws-amplify/core", + version: "6.16.1", + ecosystem: "npm", + paths: [["project", "aws-amplify", "@aws-amplify/core"]], + }, ], directDependencyNames: new Set(["aws-amplify"]), }); @@ -585,7 +613,9 @@ describe("resolveNpmTransitiveRemediation — 3-level within-range gap (#522)", targetChildVersion: "3.0.8", viaPath: ["project", "aws-amplify", "@aws-amplify/core", "js-cookie"], }); - expect(result?.reason).toContain("@aws-amplify/core@6.16.1 already allows js-cookie@3.0.8"); + expect(result?.reason).toContain( + "@aws-amplify/core@6.16.1 already allows js-cookie@3.0.8", + ); }); }); @@ -622,13 +652,25 @@ describe("resolveTransitiveRemediationViaRegistry — deep-chain within-range", vulnerabilities: [{ id: "GHSA-qjx8-664m-686j" }], severity: "high", cveAliases: [], - dependencyPaths: [["project", "aws-amplify", "@aws-amplify/core", "js-cookie"]], + dependencyPaths: [ + ["project", "aws-amplify", "@aws-amplify/core", "js-cookie"], + ], relationship: "transitive", firstFixedVersion: "3.0.7", }, packages: [ - { name: "aws-amplify", version: "6.16.3", ecosystem: "npm", paths: [["project", "aws-amplify"]] }, - { name: "@aws-amplify/core", version: "6.16.1", ecosystem: "npm", paths: [["project", "aws-amplify", "@aws-amplify/core"]] }, + { + name: "aws-amplify", + version: "6.16.3", + ecosystem: "npm", + paths: [["project", "aws-amplify"]], + }, + { + name: "@aws-amplify/core", + version: "6.16.1", + ecosystem: "npm", + paths: [["project", "aws-amplify", "@aws-amplify/core"]], + }, ], directDependencyNames: new Set(["aws-amplify"]), }); @@ -662,7 +704,10 @@ describe("resolveRecommendedParentUpgrade", () => { await expect( resolveRecommendedParentUpgrade( - createFinding({ dependencyPaths: [], pkg: { name: "lodash", version: "4.17.20", ecosystem: "npm" } }), + createFinding({ + dependencyPaths: [], + pkg: { name: "lodash", version: "4.17.20", ecosystem: "npm" }, + }), packages, ), ).resolves.toBeNull(); @@ -676,7 +721,9 @@ describe("resolveRecommendedParentUpgrade", () => { dependencyPaths: [["project", "missing-parent", "lodash"]], }); - await expect(resolveRecommendedParentUpgrade(finding, createPackages())).resolves.toBeNull(); + await expect( + resolveRecommendedParentUpgrade(finding, createPackages()), + ).resolves.toBeNull(); expect(fetchMock).not.toHaveBeenCalled(); }); @@ -698,7 +745,10 @@ describe("resolveRecommendedParentUpgrade", () => { }, }); - const result = await resolveRecommendedParentUpgrade(createFinding(), createPackages()); + const result = await resolveRecommendedParentUpgrade( + createFinding(), + createPackages(), + ); expect(result).toMatchObject({ package: "app", @@ -767,7 +817,10 @@ describe("resolveRecommendedParentUpgrade", () => { }, }); - const result = await resolveRecommendedParentUpgrade(finding, createPackages()); + const result = await resolveRecommendedParentUpgrade( + finding, + createPackages(), + ); expect(result).toMatchObject({ package: "app", @@ -812,7 +865,9 @@ describe("resolveRecommendedParentUpgrade", () => { }, }); - await expect(resolveRecommendedParentUpgrade(finding, packages)).resolves.toBeNull(); + await expect( + resolveRecommendedParentUpgrade(finding, packages), + ).resolves.toBeNull(); }); it("returns null when all newer parent versions are pre-release", async () => { @@ -833,7 +888,10 @@ describe("resolveRecommendedParentUpgrade", () => { }, }); - const result = await resolveRecommendedParentUpgrade(createFinding(), createPackages()); + const result = await resolveRecommendedParentUpgrade( + createFinding(), + createPackages(), + ); expect(result).toBeNull(); }); @@ -856,7 +914,10 @@ describe("resolveRecommendedParentUpgrade", () => { }, }); - const result = await resolveRecommendedParentUpgrade(createFinding(), createPackages()); + const result = await resolveRecommendedParentUpgrade( + createFinding(), + createPackages(), + ); expect(result).toMatchObject({ package: "app", @@ -868,7 +929,9 @@ describe("resolveRecommendedParentUpgrade", () => { const resolveRecommendedParentUpgrade = await loadResolver(); mockPackument({}, false); - await expect(resolveRecommendedParentUpgrade(createFinding(), createPackages())).resolves.toBeNull(); + await expect( + resolveRecommendedParentUpgrade(createFinding(), createPackages()), + ).resolves.toBeNull(); expect(fetchMock).toHaveBeenCalledTimes(1); }); @@ -879,7 +942,9 @@ describe("resolveRecommendedParentUpgrade", () => { const resolveRecommendedParentUpgrade = await loadResolver(); await expect( - resolveRecommendedParentUpgrade(createFinding(), createPackages(), null, { offline: true }), + resolveRecommendedParentUpgrade(createFinding(), createPackages(), null, { + offline: true, + }), ).resolves.toBeNull(); expect(fetchMock).not.toHaveBeenCalled(); }); @@ -914,13 +979,13 @@ describe("resolveRecommendedParentUpgrade", () => { }, { name: "mid", - version: "2.0.0", // old version — the one on the vulnerable path + version: "2.0.0", // old version — the one on the vulnerable path ecosystem: "npm", paths: [["project", "app", "mid"]], }, { name: "mid", - version: "3.0.0", // new version — different path, must not shadow the above + version: "3.0.0", // new version — different path, must not shadow the above ecosystem: "npm", paths: [["project", "app", "other", "mid"]], }, @@ -948,7 +1013,11 @@ describe("resolveRecommendedParentUpgrade", () => { // mid ^3.0.0 which satisfies 3.0.0, so it would not be selected. With the // correct version (2.0.0), app@2.0.0 requires mid ^3.0.0 which no longer // allows mid@2.0.0, triggering the best-effort upgrade recommendation. - const result = await resolveRecommendedParentUpgrade(finding, packages, new Set(["app"])); + const result = await resolveRecommendedParentUpgrade( + finding, + packages, + new Set(["app"]), + ); expect(result).toMatchObject({ package: "app", diff --git a/tests/parsers/bun-lock.test.ts b/tests/parsers/bun-lock.test.ts index ed512a83..e7b75bc6 100644 --- a/tests/parsers/bun-lock.test.ts +++ b/tests/parsers/bun-lock.test.ts @@ -31,9 +31,9 @@ describe("bun.lock parser", () => { }, }, packages: { - "chalk": ["chalk@5.4.1", "", {}, "sha512-abc"], + chalk: ["chalk@5.4.1", "", {}, "sha512-abc"], "@babel/core": ["@babel/core@7.29.0", "", {}, "sha512-def"], - "jest": ["jest@30.3.0", "", {}, "sha512-ghi"], + jest: ["jest@30.3.0", "", {}, "sha512-ghi"], }, }), "utf8", @@ -44,9 +44,22 @@ describe("bun.lock parser", () => { expect(packages).toEqual( expect.arrayContaining([ - expect.objectContaining({ name: "chalk", version: "5.4.1", dev: false, paths: [["project", "chalk"]] }), - expect.objectContaining({ name: "@babel/core", version: "7.29.0", dev: false }), - expect.objectContaining({ name: "jest", version: "30.3.0", dev: true }), + expect.objectContaining({ + name: "chalk", + version: "5.4.1", + dev: false, + paths: [["project", "chalk"]], + }), + expect.objectContaining({ + name: "@babel/core", + version: "7.29.0", + dev: false, + }), + expect.objectContaining({ + name: "jest", + version: "30.3.0", + dev: true, + }), ]), ); } finally { @@ -103,9 +116,9 @@ describe("bun.lock parser", () => { }, }, packages: { - "chalk": ["chalk@5.4.1", "", {}, "sha512-abc"], - "jest": ["jest@30.3.0", "", {}, "sha512-def"], - "shared": ["shared@1.0.0", "", {}, "sha512-ghi"], + chalk: ["chalk@5.4.1", "", {}, "sha512-abc"], + jest: ["jest@30.3.0", "", {}, "sha512-def"], + shared: ["shared@1.0.0", "", {}, "sha512-ghi"], }, }), "utf8", @@ -113,9 +126,9 @@ describe("bun.lock parser", () => { try { const packages = loadFromBunLock(lockPath, false); - const chalk = packages.find(p => p.name === "chalk"); - const jest = packages.find(p => p.name === "jest"); - const shared = packages.find(p => p.name === "shared"); + const chalk = packages.find((p) => p.name === "chalk"); + const jest = packages.find((p) => p.name === "jest"); + const shared = packages.find((p) => p.name === "shared"); expect(chalk?.dev).toBe(false); expect(jest?.dev).toBe(true); @@ -142,8 +155,8 @@ describe("bun.lock parser", () => { }, }, packages: { - "chalk": ["chalk@5.4.1", "", {}, "sha512-abc"], - "jest": ["jest@30.3.0", "", {}, "sha512-def"], + chalk: ["chalk@5.4.1", "", {}, "sha512-abc"], + jest: ["jest@30.3.0", "", {}, "sha512-def"], }, }), "utf8", @@ -187,11 +200,15 @@ describe("bun.lock parser", () => { try { const packages = loadFromBunLock(lockPath, false); - const axios = packages.find(pkg => pkg.name === "axios"); - const followRedirects = packages.find(pkg => pkg.name === "follow-redirects"); + const axios = packages.find((pkg) => pkg.name === "axios"); + const followRedirects = packages.find( + (pkg) => pkg.name === "follow-redirects", + ); expect(axios?.paths).toEqual([["project", "axios"]]); - expect(followRedirects?.paths).toEqual([["project", "axios", "follow-redirects"]]); + expect(followRedirects?.paths).toEqual([ + ["project", "axios", "follow-redirects"], + ]); } finally { removeDir(projectDir); } @@ -212,7 +229,12 @@ describe("bun.lock parser", () => { }, }, packages: { - "chalk": ["chalk@5.4.1", "", { "dependencies": { "ansi-styles": "^6.0.0" } }, "sha512-abc"], + chalk: [ + "chalk@5.4.1", + "", + { dependencies: { "ansi-styles": "^6.0.0" } }, + "sha512-abc", + ], "ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-def"], }, }), @@ -221,7 +243,7 @@ describe("bun.lock parser", () => { try { const packages = loadFromBunLock(lockPath, false); - const ansiStyles = packages.find(p => p.name === "ansi-styles"); + const ansiStyles = packages.find((p) => p.name === "ansi-styles"); expect(ansiStyles).toBeDefined(); expect(ansiStyles?.dev).toBe(false); } finally { @@ -241,20 +263,28 @@ describe("bun.lock parser", () => { lockPath, JSON.stringify({ lockfileVersion: 1, - workspaces: { "": { name: "test", dependencies: { "node-ipc": "9.2.3" } } }, + workspaces: { + "": { name: "test", dependencies: { "node-ipc": "9.2.3" } }, + }, packages: { - "node-ipc": ["node-ipc@9.2.3", "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", {}, ""], + "node-ipc": [ + "node-ipc@9.2.3", + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + {}, + "", + ], }, }), "utf8", ); try { const packages = loadFromBunLock(lockPath, false); - const nodeIpc = packages.find(p => p.name === "node-ipc"); - expect(nodeIpc?.resolvedUrl).toBe("https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz"); + const nodeIpc = packages.find((p) => p.name === "node-ipc"); + expect(nodeIpc?.resolvedUrl).toBe( + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + ); } finally { removeDir(projectDir); } }); - }); diff --git a/tests/parsers/load-packages.test.ts b/tests/parsers/load-packages.test.ts index 2a7358fb..118d275d 100644 --- a/tests/parsers/load-packages.test.ts +++ b/tests/parsers/load-packages.test.ts @@ -22,8 +22,10 @@ describe("loadPackages", () => { path.join(projectDir, "bun.lock"), JSON.stringify({ lockfileVersion: 1, - workspaces: { "": { name: "fixture", dependencies: { chalk: "^5.0.0" } } }, - packages: { "chalk": ["chalk@5.4.1", "", {}, "sha512-abc"] }, + workspaces: { + "": { name: "fixture", dependencies: { chalk: "^5.0.0" } }, + }, + packages: { chalk: ["chalk@5.4.1", "", {}, "sha512-abc"] }, }), "utf8", ); @@ -36,7 +38,9 @@ describe("loadPackages", () => { expect(result.mode).toBe("resolved-lockfile"); expect(result.warnings).toEqual([]); expect(result.packages).toEqual( - expect.arrayContaining([expect.objectContaining({ name: "chalk", version: "5.4.1" })]), + expect.arrayContaining([ + expect.objectContaining({ name: "chalk", version: "5.4.1" }), + ]), ); } finally { removeDir(projectDir); @@ -82,7 +86,9 @@ packages: expect(path.basename(result.filePath ?? "")).toBe("package-lock.json"); expect(result.warnings).toEqual([]); expect(result.packages).toEqual( - expect.arrayContaining([expect.objectContaining({ name: "chalk", version: "5.4.1" })]), + expect.arrayContaining([ + expect.objectContaining({ name: "chalk", version: "5.4.1" }), + ]), ); } finally { removeDir(projectDir); @@ -112,7 +118,9 @@ packages: expect(result.mode).toBe("resolved-lockfile"); expect(result.warnings).toEqual([]); expect(result.packages).toEqual( - expect.arrayContaining([expect.objectContaining({ name: "lodash", version: "4.17.21" })]), + expect.arrayContaining([ + expect.objectContaining({ name: "lodash", version: "4.17.21" }), + ]), ); } finally { removeDir(projectDir); @@ -153,7 +161,7 @@ packages: expect(result.packages).toEqual( expect.arrayContaining([expect.objectContaining({ name: "lodash" })]), ); - expect(result.packages.map(p => p.name)).not.toContain("express"); + expect(result.packages.map((p) => p.name)).not.toContain("express"); } finally { removeDir(projectDir); } @@ -172,7 +180,11 @@ packages: }), "utf8", ); - fs.writeFileSync(path.join(projectDir, ".npmrc"), "package-lock=false\n", "utf8"); + fs.writeFileSync( + path.join(projectDir, ".npmrc"), + "package-lock=false\n", + "utf8", + ); try { const result = loadPackages(projectDir, false, 3); @@ -180,13 +192,17 @@ packages: expect(result.mode).toBe("manifest-fallback"); expect(result.source).toBe("package-json"); expect(result.packages).toEqual( - expect.arrayContaining([expect.objectContaining({ name: "chalk", version: "5.4.1" })]), + expect.arrayContaining([ + expect.objectContaining({ name: "chalk", version: "5.4.1" }), + ]), ); expect(result.skippedDependencies).toContain("dependencies:debug@^4.3.0"); expect(result.warnings).toEqual( expect.arrayContaining([ "No supported lockfile was found, so the scanner fell back to package.json.", - expect.stringContaining("This repo disables package-lock generation in .npmrc."), + expect.stringContaining( + "This repo disables package-lock generation in .npmrc.", + ), ]), ); } finally { diff --git a/tests/parsers/npm-lock-graph.test.ts b/tests/parsers/npm-lock-graph.test.ts index fc705d84..08478143 100644 --- a/tests/parsers/npm-lock-graph.test.ts +++ b/tests/parsers/npm-lock-graph.test.ts @@ -49,7 +49,10 @@ describe("npm lock graph extraction", () => { try { const graph = loadNpmLockGraph(lockPath); const mochaNodeId = graph.nodeIdsFor("mocha", "10.0.0")[0]; - const serializeNodeId = graph.nodeIdsFor("serialize-javascript", "6.0.2")[0]; + const serializeNodeId = graph.nodeIdsFor( + "serialize-javascript", + "6.0.2", + )[0]; expect(graph.entryPackages).toContain(mochaNodeId); expect(graph.parentsFor(serializeNodeId)).toContain(mochaNodeId); @@ -147,7 +150,9 @@ describe("npm lock graph extraction", () => { const graph = loadNpmLockGraph(lockPath); const mochaNodeId = graph.nodeIdsFor("mocha", "10.0.0")[0]; - expect(graph.rangeFor(mochaNodeId, "serialize-javascript")).toBe("^6.0.0"); + expect(graph.rangeFor(mochaNodeId, "serialize-javascript")).toBe( + "^6.0.0", + ); } finally { removeDir(projectDir); } @@ -202,10 +207,7 @@ describe("npm lock graph extraction", () => { expect(sharedNodeIds).toHaveLength(2); expect(sharedNodeIds.map((nodeId) => graph.parentsFor(nodeId))).toEqual( - expect.arrayContaining([ - [alphaNodeId], - [betaNodeId], - ]), + expect.arrayContaining([[alphaNodeId], [betaNodeId]]), ); expect(sharedNodeIds.map((nodeId) => graph.pathsFor(nodeId))).toEqual( expect.arrayContaining([ @@ -275,8 +277,16 @@ describe("npm lock graph extraction", () => { expect(graph.childrenFor(parentNodeId)).toEqual([childNodeId]); expect(graph.parentsFor(reactNodeId)).not.toContain(parentNodeId); expect(graph.parentsFor(jestNodeId)).not.toContain(parentNodeId); - expect(graph.pathsFor(reactNodeId)).not.toContainEqual(["project", "parent", "react"]); - expect(graph.pathsFor(jestNodeId)).not.toContainEqual(["project", "parent", "jest"]); + expect(graph.pathsFor(reactNodeId)).not.toContainEqual([ + "project", + "parent", + "react", + ]); + expect(graph.pathsFor(jestNodeId)).not.toContainEqual([ + "project", + "parent", + "jest", + ]); } finally { removeDir(projectDir); } @@ -331,7 +341,11 @@ describe("npm lock graph extraction", () => { }), ); expect(graph.parentsFor(sharedNodeId)).toContain(workspaceNodeId); - expect(graph.pathsFor(sharedNodeId)).toContainEqual(["project", "workspaceA", "shared"]); + expect(graph.pathsFor(sharedNodeId)).toContainEqual([ + "project", + "workspaceA", + "shared", + ]); } finally { removeDir(projectDir); } @@ -379,8 +393,8 @@ describe("npm lock graph extraction", () => { const parents = graph.parentsFor(childId); // Each should appear exactly once despite two declarations of the same edge - expect(children.filter(id => id === childId)).toHaveLength(1); - expect(parents.filter(id => id === parentId)).toHaveLength(1); + expect(children.filter((id) => id === childId)).toHaveLength(1); + expect(parents.filter((id) => id === parentId)).toHaveLength(1); } finally { removeDir(projectDir); } diff --git a/tests/parsers/npm-lock.test.ts b/tests/parsers/npm-lock.test.ts index 1727480e..3dbe242e 100644 --- a/tests/parsers/npm-lock.test.ts +++ b/tests/parsers/npm-lock.test.ts @@ -41,13 +41,21 @@ describe("package-lock parser", () => { expect(allPackages).toEqual( expect.arrayContaining([ - expect.objectContaining({ name: "chalk", version: "5.4.1", paths: [["project", "chalk"]] }), + expect.objectContaining({ + name: "chalk", + version: "5.4.1", + paths: [["project", "chalk"]], + }), expect.objectContaining({ name: "loose-envify", version: "1.4.0", paths: [["project", "react", "loose-envify"]], }), - expect.objectContaining({ name: "jest", version: "30.3.0", dev: true }), + expect.objectContaining({ + name: "jest", + version: "30.3.0", + dev: true, + }), ]), ); expect(prodPackages).not.toEqual( @@ -58,7 +66,6 @@ describe("package-lock parser", () => { } }); - it("preserves resolved URL from package-lock.json on PackageRef", () => { const projectDir = createTempProjectDir(); const lockPath = path.join(projectDir, "package-lock.json"); @@ -69,22 +76,28 @@ describe("package-lock parser", () => { name: "test", lockfileVersion: 3, packages: { - "": { name: "test", version: "1.0.0", dependencies: { "lodash": "^4.17.21" } }, + "": { + name: "test", + version: "1.0.0", + dependencies: { lodash: "^4.17.21" }, + }, "node_modules/lodash": { version: "4.17.21", resolved: "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - integrity: "sha512-abc" - } - } + integrity: "sha512-abc", + }, + }, }), "utf8", ); try { const packages = loadFromPackageLock(lockPath, false); - const lodash = packages.find(p => p.name === "lodash"); + const lodash = packages.find((p) => p.name === "lodash"); - expect(lodash?.resolvedUrl).toBe("https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"); + expect(lodash?.resolvedUrl).toBe( + "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + ); } finally { removeDir(projectDir); } @@ -102,7 +115,9 @@ describe("package-lock parser", () => { packages: { "": { name: "fixture", version: "1.0.0" }, "server/node_modules/workspace-proof-parent": { version: "1.0.0" }, - "server/node_modules/workspace-proof-parent/node_modules/braces": { version: "3.0.2" }, + "server/node_modules/workspace-proof-parent/node_modules/braces": { + version: "3.0.2", + }, }, }), "utf8", @@ -163,7 +178,7 @@ describe("package-lock parser", () => { try { const packages = loadFromPackageLock(lockPath, false); - const pathToRegexp = packages.find(p => p.name === "path-to-regexp"); + const pathToRegexp = packages.find((p) => p.name === "path-to-regexp"); expect(pathToRegexp).toBeDefined(); expect(pathToRegexp?.paths).toContainEqual([ @@ -179,13 +194,16 @@ describe("package-lock parser", () => { it("parses 3-level deep dependency chain from real fixture file", () => { const __dirname = path.dirname(fileURLToPath(import.meta.url)); - const fixturePath = path.resolve(__dirname, "../../examples/deep-chain-validated/package-lock.json"); + const fixturePath = path.resolve( + __dirname, + "../../examples/deep-chain-validated/package-lock.json", + ); const packages = loadFromPackageLock(fixturePath, false); - const express = packages.find(p => p.name === "express"); - const send = packages.find(p => p.name === "send"); - const mimeTypes = packages.find(p => p.name === "mime-types"); + const express = packages.find((p) => p.name === "express"); + const send = packages.find((p) => p.name === "send"); + const mimeTypes = packages.find((p) => p.name === "mime-types"); expect(express).toBeDefined(); expect(express?.version).toBe("4.17.0"); @@ -197,7 +215,12 @@ describe("package-lock parser", () => { expect(mimeTypes).toBeDefined(); expect(mimeTypes?.version).toBe("1.0.0"); - expect(mimeTypes?.paths).toContainEqual(["project", "express", "send", "mime-types"]); + expect(mimeTypes?.paths).toContainEqual([ + "project", + "express", + "send", + "mime-types", + ]); }); it("falls back to legacy dependencies when packages metadata is absent", () => { @@ -228,7 +251,11 @@ describe("package-lock parser", () => { expect(packages).toEqual( expect.arrayContaining([ - expect.objectContaining({ name: "react", version: "18.2.0", paths: [["project", "react"]] }), + expect.objectContaining({ + name: "react", + version: "18.2.0", + paths: [["project", "react"]], + }), expect.objectContaining({ name: "loose-envify", version: "1.4.0", diff --git a/tests/parsers/package-json.test.ts b/tests/parsers/package-json.test.ts index 905876c5..60689e69 100644 --- a/tests/parsers/package-json.test.ts +++ b/tests/parsers/package-json.test.ts @@ -42,9 +42,24 @@ describe("package.json parser", () => { expect(result.packages).toEqual( expect.arrayContaining([ - expect.objectContaining({ name: "chalk", version: "5.4.1", dev: false, paths: [["project", "chalk"]] }), - expect.objectContaining({ name: "yaml", version: "2.7.1", dev: false, paths: [["project", "yaml"]] }), - expect.objectContaining({ name: "jest", version: "30.3.0", dev: true, paths: [["project", "jest"]] }), + expect.objectContaining({ + name: "chalk", + version: "5.4.1", + dev: false, + paths: [["project", "chalk"]], + }), + expect.objectContaining({ + name: "yaml", + version: "2.7.1", + dev: false, + paths: [["project", "yaml"]], + }), + expect.objectContaining({ + name: "jest", + version: "30.3.0", + dev: true, + paths: [["project", "jest"]], + }), ]), ); expect(result.skippedDependencies).toEqual( @@ -75,7 +90,11 @@ describe("package.json parser", () => { const result = loadFromPackageJson(packageJsonPath, true); expect(result.packages).toEqual([ - expect.objectContaining({ name: "chalk", version: "5.4.1", dev: false }), + expect.objectContaining({ + name: "chalk", + version: "5.4.1", + dev: false, + }), ]); expect(result.packages).not.toEqual( expect.arrayContaining([expect.objectContaining({ name: "jest" })]), diff --git a/tests/parsers/pnpm-lock.test.ts b/tests/parsers/pnpm-lock.test.ts index 3c8a546a..d38775f9 100644 --- a/tests/parsers/pnpm-lock.test.ts +++ b/tests/parsers/pnpm-lock.test.ts @@ -48,13 +48,21 @@ packages: expect(allPackages).toEqual( expect.arrayContaining([ - expect.objectContaining({ name: "react", version: "18.2.0", paths: [["project", "react"]] }), + expect.objectContaining({ + name: "react", + version: "18.2.0", + paths: [["project", "react"]], + }), expect.objectContaining({ name: "loose-envify", version: "1.4.0", paths: [["project", "react", "loose-envify"]], }), - expect.objectContaining({ name: "jest", version: "30.3.0", dev: true }), + expect.objectContaining({ + name: "jest", + version: "30.3.0", + dev: true, + }), ]), ); expect(prodPackages).not.toEqual( @@ -106,15 +114,27 @@ snapshots: expect(allPackages).toEqual( expect.arrayContaining([ - expect.objectContaining({ name: "react", version: "18.2.0", paths: [["project", "react"]] }), + expect.objectContaining({ + name: "react", + version: "18.2.0", + paths: [["project", "react"]], + }), expect.objectContaining({ name: "loose-envify", version: "1.4.0", paths: [["project", "react", "loose-envify"]], }), expect.objectContaining({ name: "handlebars", version: "4.7.8" }), - expect.objectContaining({ name: "@scope/lib", version: "1.0.0", paths: [["project", "@scope/lib"]] }), - expect.objectContaining({ name: "jest", version: "30.3.0", dev: true }), + expect.objectContaining({ + name: "@scope/lib", + version: "1.0.0", + paths: [["project", "@scope/lib"]], + }), + expect.objectContaining({ + name: "jest", + version: "30.3.0", + dev: true, + }), ]), ); expect(prodPackages).not.toEqual( @@ -160,12 +180,20 @@ snapshots: try { const packages = loadFromPnpmLock(lockPath, false); - const vm2 = packages.find(p => p.name === "vm2" && p.version === "3.9.19"); + const vm2 = packages.find( + (p) => p.name === "vm2" && p.version === "3.9.19", + ); expect(vm2).toBeDefined(); expect(vm2?.paths).toEqual( expect.arrayContaining([ - ["project", "vercel", "@vercel/remix-builder", "@vercel/remix-run-dev", "vm2"], + [ + "project", + "vercel", + "@vercel/remix-builder", + "@vercel/remix-run-dev", + "vm2", + ], ]), ); } finally { @@ -215,7 +243,9 @@ snapshots: try { const packages = loadFromPnpmLock(lockPath, false); - const picomatch = packages.find(pkg => pkg.name === "picomatch" && pkg.version === "4.0.3"); + const picomatch = packages.find( + (pkg) => pkg.name === "picomatch" && pkg.version === "4.0.3", + ); expect(picomatch?.paths).toEqual([ ["project", "lint-staged", "picomatch"], @@ -262,9 +292,11 @@ snapshots: try { const packages = loadFromPnpmLock(lockPath, false); - const axios = packages.find(p => p.name === "axios"); - const jest = packages.find(p => p.name === "jest"); - const followRedirects = packages.find(p => p.name === "follow-redirects"); + const axios = packages.find((p) => p.name === "axios"); + const jest = packages.find((p) => p.name === "jest"); + const followRedirects = packages.find( + (p) => p.name === "follow-redirects", + ); expect(axios?.dev).not.toBe(true); expect(jest?.dev).toBe(true); @@ -289,8 +321,10 @@ snapshots: ); try { const packages = loadFromPnpmLock(lockPath, false); - const nodeIpc = packages.find(p => p.name === "node-ipc"); - expect(nodeIpc?.resolvedUrl).toBe("https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz"); + const nodeIpc = packages.find((p) => p.name === "node-ipc"); + expect(nodeIpc?.resolvedUrl).toBe( + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + ); } finally { removeDir(projectDir); } @@ -311,11 +345,12 @@ snapshots: ); try { const packages = loadFromPnpmLock(lockPath, false); - const nodeIpc = packages.find(p => p.name === "node-ipc"); - expect(nodeIpc?.resolvedUrl).toBe("https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz"); + const nodeIpc = packages.find((p) => p.name === "node-ipc"); + expect(nodeIpc?.resolvedUrl).toBe( + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + ); } finally { removeDir(projectDir); } }); - }); diff --git a/tests/parsers/yarn-lock.test.ts b/tests/parsers/yarn-lock.test.ts index 2bf67ffe..b20309af 100644 --- a/tests/parsers/yarn-lock.test.ts +++ b/tests/parsers/yarn-lock.test.ts @@ -43,8 +43,15 @@ chalk@^5.0.0: expect(packages).toEqual( expect.arrayContaining([ - expect.objectContaining({ name: "chalk", version: "5.4.1", paths: [["project", "chalk"]] }), - expect.objectContaining({ name: "@babel/code-frame", version: "7.24.0" }), + expect.objectContaining({ + name: "chalk", + version: "5.4.1", + paths: [["project", "chalk"]], + }), + expect.objectContaining({ + name: "@babel/code-frame", + version: "7.24.0", + }), ]), ); } finally { @@ -77,11 +84,15 @@ follow-redirects@^1.10.0: try { const packages = loadFromYarnLock(lockPath); - const axios = packages.find(pkg => pkg.name === "axios"); - const followRedirects = packages.find(pkg => pkg.name === "follow-redirects"); + const axios = packages.find((pkg) => pkg.name === "axios"); + const followRedirects = packages.find( + (pkg) => pkg.name === "follow-redirects", + ); expect(axios?.paths).toEqual([["project", "axios"]]); - expect(followRedirects?.paths).toEqual([["project", "axios", "follow-redirects"]]); + expect(followRedirects?.paths).toEqual([ + ["project", "axios", "follow-redirects"], + ]); } finally { removeDir(projectDir); } @@ -117,7 +128,9 @@ js-cookie@^3.0.5: try { const packages = loadFromYarnLock(lockPath); - const jsCookie = packages.find(pkg => pkg.name === "js-cookie" && pkg.version === "3.0.6"); + const jsCookie = packages.find( + (pkg) => pkg.name === "js-cookie" && pkg.version === "3.0.6", + ); expect(jsCookie?.paths).toEqual([ ["project", "aws-amplify", "@aws-amplify/core", "js-cookie"], @@ -138,31 +151,31 @@ js-cookie@^3.0.5: ); const lockContent = [ - '__metadata:', - ' version: 8', - ' cacheKey: 10c0', - '', + "__metadata:", + " version: 8", + " cacheKey: 10c0", + "", '"lodash@npm:^4.17.0, lodash@npm:^4.17.21":', - ' version: 4.17.21', + " version: 4.17.21", ' resolution: "lodash@npm:4.17.21"', - ' checksum: 10c0/abc123', - ' languageName: node', - ' linkType: hard', - '', + " checksum: 10c0/abc123", + " languageName: node", + " linkType: hard", + "", '"@babel/core@npm:^7.0.0":', - ' version: 7.23.5', + " version: 7.23.5", ' resolution: "@babel/core@npm:7.23.5"', - ' languageName: node', - ' linkType: hard', - '', + " languageName: node", + " linkType: hard", + "", '"workspace-only@workspace:.":', - ' version: 0.0.0-use.local', + " version: 0.0.0-use.local", ' resolution: "workspace-only@workspace:."', - ' languageName: unknown', - ' linkType: soft', - ].join('\n'); + " languageName: unknown", + " linkType: soft", + ].join("\n"); - fs.writeFileSync(lockPath, lockContent, 'utf8'); + fs.writeFileSync(lockPath, lockContent, "utf8"); try { const packages = loadFromYarnLock(lockPath); @@ -170,8 +183,12 @@ js-cookie@^3.0.5: expect(packages).toHaveLength(2); expect(packages).toEqual( expect.arrayContaining([ - expect.objectContaining({ name: 'lodash', version: '4.17.21', paths: [['project', 'lodash']] }), - expect.objectContaining({ name: '@babel/core', version: '7.23.5' }), + expect.objectContaining({ + name: "lodash", + version: "4.17.21", + paths: [["project", "lodash"]], + }), + expect.objectContaining({ name: "@babel/core", version: "7.23.5" }), ]), ); } finally { @@ -210,9 +227,9 @@ jest-runner@^29.0.0: try { const packages = loadFromYarnLock(lockPath); - const axios = packages.find(p => p.name === "axios"); - const jest = packages.find(p => p.name === "jest"); - const jestRunner = packages.find(p => p.name === "jest-runner"); + const axios = packages.find((p) => p.name === "axios"); + const jest = packages.find((p) => p.name === "jest"); + const jestRunner = packages.find((p) => p.name === "jest-runner"); expect(axios?.dev).not.toBe(true); expect(jest?.dev).toBe(true); @@ -255,7 +272,9 @@ follow-redirects@^1.10.0: try { const packages = loadFromYarnLock(lockPath); - const followRedirects = packages.find(p => p.name === "follow-redirects"); + const followRedirects = packages.find( + (p) => p.name === "follow-redirects", + ); expect(followRedirects?.dev).not.toBe(true); } finally { removeDir(projectDir); @@ -277,11 +296,12 @@ follow-redirects@^1.10.0: ); try { const packages = loadFromYarnLock(lockPath); - const nodeIpc = packages.find(p => p.name === "node-ipc"); - expect(nodeIpc?.resolvedUrl).toBe("https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz"); + const nodeIpc = packages.find((p) => p.name === "node-ipc"); + expect(nodeIpc?.resolvedUrl).toBe( + "https://npm.internal.example.com/node-ipc/-/node-ipc-9.2.3.tgz", + ); } finally { removeDir(projectDir); } }); - }); diff --git a/tests/scanner-cache.test.ts b/tests/scanner-cache.test.ts index 4361a366..3ca549ff 100644 --- a/tests/scanner-cache.test.ts +++ b/tests/scanner-cache.test.ts @@ -72,7 +72,11 @@ describe("scanPackages cache behavior", () => { getVulnMock.mockResolvedValue(detail); try { - const firstFindings = await scanPackages([pkg], 100, createOptions(cacheDir)); + const firstFindings = await scanPackages( + [pkg], + 100, + createOptions(cacheDir), + ); expect(firstFindings).toHaveLength(1); expect(firstFindings[0]?.vulnerabilities).toEqual([detail]); expect(queryBatchMock).toHaveBeenCalledTimes(1); @@ -81,7 +85,11 @@ describe("scanPackages cache behavior", () => { queryBatchMock.mockClear(); getVulnMock.mockClear(); - const secondFindings = await scanPackages([pkg], 100, createOptions(cacheDir)); + const secondFindings = await scanPackages( + [pkg], + 100, + createOptions(cacheDir), + ); expect(secondFindings).toHaveLength(1); expect(secondFindings[0]?.vulnerabilities).toEqual([detail]); expect(queryBatchMock).not.toHaveBeenCalled(); @@ -105,7 +113,10 @@ describe("scanPackages cache behavior", () => { "OSV-NULL": null, }, queryEntries: { - "npm:minimist@0.0.8": { vulnIds: ["OSV-NULL"], cachedAt: new Date().toISOString() }, + "npm:minimist@0.0.8": { + vulnIds: ["OSV-NULL"], + cachedAt: new Date().toISOString(), + }, }, }), "utf8", @@ -166,7 +177,11 @@ describe("scanPackages cache behavior", () => { version: "1.0.0", lockfileVersion: 3, packages: { - "": { name: "fixture", version: "1.0.0", dependencies: { mocha: "^10.0.0" } }, + "": { + name: "fixture", + version: "1.0.0", + dependencies: { mocha: "^10.0.0" }, + }, "node_modules/mocha": { name: "mocha", version: "10.0.0", @@ -294,7 +309,12 @@ describe("scanPackages cache behavior", () => { affected: [ { package: { ecosystem: "npm", name: "tar" }, - ranges: [{ type: "SEMVER", events: [{ introduced: "0" }, { fixed: "1.0.2" }] }], + ranges: [ + { + type: "SEMVER", + events: [{ introduced: "0" }, { fixed: "1.0.2" }], + }, + ], }, ], }; @@ -304,7 +324,12 @@ describe("scanPackages cache behavior", () => { affected: [ { package: { ecosystem: "npm", name: "tar" }, - ranges: [{ type: "SEMVER", events: [{ introduced: "1.0.2" }, { fixed: "1.0.4" }] }], + ranges: [ + { + type: "SEMVER", + events: [{ introduced: "1.0.2" }, { fixed: "1.0.4" }], + }, + ], }, ], }; @@ -328,9 +353,15 @@ describe("scanPackages cache behavior", () => { expect(findings).toHaveLength(1); expect(findings[0]?.firstFixedVersion).toBe("1.0.2"); expect(findings[0]?.validatedFirstFixedVersion).toBe("1.0.4"); - expect(findings[0]?.fixVersionValidationNote).toContain("scanned 4 package versions above current version"); - expect(findings[0]?.fixVersionValidationNote).toContain("(3 still known vulnerable)"); - expect(findings[0]?.fixVersionValidationNote).toContain("lowest known non-vulnerable version 1.0.4"); + expect(findings[0]?.fixVersionValidationNote).toContain( + "scanned 4 package versions above current version", + ); + expect(findings[0]?.fixVersionValidationNote).toContain( + "(3 still known vulnerable)", + ); + expect(findings[0]?.fixVersionValidationNote).toContain( + "lowest known non-vulnerable version 1.0.4", + ); } finally { removeDir(cacheDir); } @@ -420,7 +451,12 @@ describe("scanPackages cache behavior", () => { affected: [ { package: { ecosystem: "npm", name: "tar" }, - ranges: [{ type: "SEMVER", events: [{ introduced: "0" }, { fixed: "2.0.1" }] }], + ranges: [ + { + type: "SEMVER", + events: [{ introduced: "0" }, { fixed: "2.0.1" }], + }, + ], }, ], }; @@ -464,7 +500,11 @@ describe("scanPackages cache behavior", () => { const goodDetail: OsvVuln = { id: "OSV-GOOD" }; queryBatchMock.mockResolvedValue([ - { package: pkg.name, version: pkg.version, vulnerabilities: [{ id: "OSV-GOOD" }, { id: "OSV-BAD" }] }, + { + package: pkg.name, + version: pkg.version, + vulnerabilities: [{ id: "OSV-GOOD" }, { id: "OSV-BAD" }], + }, ]); // OSV-GOOD succeeds, OSV-BAD fails getVulnMock.mockImplementation(async (id: string) => { @@ -503,7 +543,11 @@ describe("scanPackages cache behavior", () => { ); queryBatchMock.mockResolvedValue([ - { package: pkg.name, version: pkg.version, vulnerabilities: [{ id: "OSV-CACHED" }, { id: "OSV-FRESH" }] }, + { + package: pkg.name, + version: pkg.version, + vulnerabilities: [{ id: "OSV-CACHED" }, { id: "OSV-FRESH" }], + }, ]); getVulnMock.mockResolvedValue(freshDetail); @@ -511,7 +555,9 @@ describe("scanPackages cache behavior", () => { const findings = await scanPackages([pkg], 100, createOptions(cacheDir)); expect(findings).toHaveLength(1); // Both details should appear in the finding - expect(findings[0]?.vulnerabilities).toEqual(expect.arrayContaining([cachedDetail, freshDetail])); + expect(findings[0]?.vulnerabilities).toEqual( + expect.arrayContaining([cachedDetail, freshDetail]), + ); // Only the uncached ID should have been fetched expect(getVulnMock).toHaveBeenCalledTimes(1); expect(getVulnMock).toHaveBeenCalledWith("OSV-FRESH"); @@ -523,11 +569,15 @@ describe("scanPackages cache behavior", () => { it("fires all batch requests in parallel rather than sequentially", async () => { const cacheDir = createTempCacheDir(); const packages = Array.from({ length: 3 }, (_, i) => - createPackage(`pkg-${i}`, "1.0.0") + createPackage(`pkg-${i}`, "1.0.0"), ); queryBatchMock.mockImplementation(async (pkgs: PackageRef[]) => { - return pkgs.map(() => ({ package: pkgs[0]?.name, version: "1.0.0", vulnerabilities: [] })); + return pkgs.map(() => ({ + package: pkgs[0]?.name, + version: "1.0.0", + vulnerabilities: [], + })); }); try { @@ -582,14 +632,21 @@ describe("scanPackages cache behavior", () => { createdAt: staleTimestamp, entries: {}, queryEntries: { - "npm:stale-vuln@2.0.0": { vulnIds: ["OSV-OLD"], cachedAt: staleTimestamp }, + "npm:stale-vuln@2.0.0": { + vulnIds: ["OSV-OLD"], + cachedAt: staleTimestamp, + }, }, }), "utf8", ); queryBatchMock.mockResolvedValue([ - { package: pkg.name, version: pkg.version, vulnerabilities: [{ id: "OSV-OLD" }, { id: "OSV-NEW" }] }, + { + package: pkg.name, + version: pkg.version, + vulnerabilities: [{ id: "OSV-OLD" }, { id: "OSV-NEW" }], + }, ]); getVulnMock.mockResolvedValue({ id: "OSV-OLD" }); @@ -613,7 +670,10 @@ describe("scanPackages cache behavior", () => { createdAt: new Date().toISOString(), entries: {}, queryEntries: { - "npm:lodash@4.17.20": { vulnIds: ["OSV-CACHED"], cachedAt: new Date().toISOString() }, + "npm:lodash@4.17.20": { + vulnIds: ["OSV-CACHED"], + cachedAt: new Date().toISOString(), + }, }, }), "utf8", @@ -624,7 +684,10 @@ describe("scanPackages cache behavior", () => { ]); try { - await scanPackages([pkg], 100, { ...createOptions(cacheDir), noCache: true }); + await scanPackages([pkg], 100, { + ...createOptions(cacheDir), + noCache: true, + }); expect(queryBatchMock).toHaveBeenCalledTimes(1); } finally { removeDir(cacheDir); @@ -636,12 +699,19 @@ describe("scanPackages cache behavior", () => { const pkg = createPackage("debug", "3.0.0"); queryBatchMock.mockResolvedValue([ - { package: pkg.name, version: pkg.version, vulnerabilities: [{ id: "OSV-999" }] }, + { + package: pkg.name, + version: pkg.version, + vulnerabilities: [{ id: "OSV-999" }], + }, ]); getVulnMock.mockResolvedValue({ id: "OSV-999" }); try { - await scanPackages([pkg], 100, { ...createOptions(cacheDir), noCache: true }); + await scanPackages([pkg], 100, { + ...createOptions(cacheDir), + noCache: true, + }); const cache = loadCache(cacheDir); expect(cache.queryEntries["npm:debug@3.0.0"]).toMatchObject({ vulnIds: ["OSV-999"], @@ -659,7 +729,8 @@ describe("scanPackages cache behavior", () => { version: "1.0.0", ecosystem: "npm", paths: [["root", "evil-pkg"]], - resolvedUrl: "https://npm.mycompany.internal/evil-pkg/-/evil-pkg-1.0.0.tgz", + resolvedUrl: + "https://npm.mycompany.internal/evil-pkg/-/evil-pkg-1.0.0.tgz", }; const detail: OsvVuln = { id: "MAL-2026-0001", @@ -667,7 +738,11 @@ describe("scanPackages cache behavior", () => { }; queryBatchMock.mockResolvedValue([ - { package: pkg.name, version: pkg.version, vulnerabilities: [{ id: "MAL-2026-0001" }] }, + { + package: pkg.name, + version: pkg.version, + vulnerabilities: [{ id: "MAL-2026-0001" }], + }, ]); getVulnMock.mockResolvedValue(detail); fetchMock.mockResolvedValue({ ok: false, status: 404 }); @@ -696,7 +771,11 @@ describe("scanPackages cache behavior", () => { }; queryBatchMock.mockResolvedValue([ - { package: pkg.name, version: pkg.version, vulnerabilities: [{ id: "MAL-2026-0002" }] }, + { + package: pkg.name, + version: pkg.version, + vulnerabilities: [{ id: "MAL-2026-0002" }], + }, ]); getVulnMock.mockResolvedValue(detail); fetchMock.mockResolvedValue({ ok: false, status: 404 }); @@ -724,7 +803,11 @@ describe("scanPackages cache behavior", () => { }; queryBatchMock.mockResolvedValue([ - { package: pkg.name, version: pkg.version, vulnerabilities: [{ id: "MAL-2026-0003" }] }, + { + package: pkg.name, + version: pkg.version, + vulnerabilities: [{ id: "MAL-2026-0003" }], + }, ]); getVulnMock.mockResolvedValue(detail); fetchMock.mockResolvedValue({ ok: false, status: 404 }); @@ -745,7 +828,8 @@ describe("scanPackages cache behavior", () => { version: "1.0.0", ecosystem: "npm", paths: [["root", "vulnerable-pkg"]], - resolvedUrl: "https://npm.mycompany.internal/vulnerable-pkg/-/vulnerable-pkg-1.0.0.tgz", + resolvedUrl: + "https://npm.mycompany.internal/vulnerable-pkg/-/vulnerable-pkg-1.0.0.tgz", }; const detail: OsvVuln = { id: "CVE-2026-9999", @@ -753,7 +837,11 @@ describe("scanPackages cache behavior", () => { }; queryBatchMock.mockResolvedValue([ - { package: pkg.name, version: pkg.version, vulnerabilities: [{ id: "CVE-2026-9999" }] }, + { + package: pkg.name, + version: pkg.version, + vulnerabilities: [{ id: "CVE-2026-9999" }], + }, ]); getVulnMock.mockResolvedValue(detail); fetchMock.mockResolvedValue({ ok: false, status: 404 }); diff --git a/tests/test-utils.ts b/tests/test-utils.ts index f1eb7994..8ed430d4 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -2,7 +2,12 @@ import fs from "node:fs"; export function removeDir(dirPath: string): void { try { - fs.rmSync(dirPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }); + fs.rmSync(dirPath, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 200, + }); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") { throw error; diff --git a/tests/time.test.ts b/tests/time.test.ts index 522c8867..eb5ac6d3 100644 --- a/tests/time.test.ts +++ b/tests/time.test.ts @@ -7,7 +7,9 @@ describe("relativeAge", () => { }); it("returns minutes when timestamp is within the last hour", () => { - expect(relativeAge(Date.now() - 5 * 60 * 1000)).toBe("synced 5 minutes ago"); + expect(relativeAge(Date.now() - 5 * 60 * 1000)).toBe( + "synced 5 minutes ago", + ); }); it("uses singular minute for exactly one minute", () => { @@ -15,7 +17,9 @@ describe("relativeAge", () => { }); it("returns hours when timestamp is within the last day", () => { - expect(relativeAge(Date.now() - 3 * 60 * 60 * 1000)).toBe("synced 3 hours ago"); + expect(relativeAge(Date.now() - 3 * 60 * 60 * 1000)).toBe( + "synced 3 hours ago", + ); }); it("uses singular hour for exactly one hour", () => { @@ -23,11 +27,15 @@ describe("relativeAge", () => { }); it("returns days when timestamp is older than a day", () => { - expect(relativeAge(Date.now() - 2 * 24 * 60 * 60 * 1000)).toBe("synced 2 days ago"); + expect(relativeAge(Date.now() - 2 * 24 * 60 * 60 * 1000)).toBe( + "synced 2 days ago", + ); }); it("uses singular day for exactly one day", () => { - expect(relativeAge(Date.now() - 24 * 60 * 60 * 1000)).toBe("synced 1 day ago"); + expect(relativeAge(Date.now() - 24 * 60 * 60 * 1000)).toBe( + "synced 1 day ago", + ); }); it("clamps future timestamps to 'just synced'", () => { @@ -46,6 +54,8 @@ describe("formatAdvisoryDbFreshness", () => { it("includes the relative age and the raw timestamp when parseable", () => { const iso = new Date(Date.now() - 5 * 60 * 1000).toISOString(); - expect(stripAnsi(formatAdvisoryDbFreshness(iso))).toBe(`synced 5 minutes ago (${iso})`); + expect(stripAnsi(formatAdvisoryDbFreshness(iso))).toBe( + `synced 5 minutes ago (${iso})`, + ); }); }); diff --git a/tests/transitive-chain-resolver.test.ts b/tests/transitive-chain-resolver.test.ts index b0fdf79f..0a9f8bf7 100644 --- a/tests/transitive-chain-resolver.test.ts +++ b/tests/transitive-chain-resolver.test.ts @@ -48,17 +48,18 @@ describe("resolveChainFix", () => { }); it("returns null when direct dep is not in installedVersions", async () => { - const result = await resolveChainFix( - makeFinding(), - new Map(), - "npm", - { offline: false }, - ); + const result = await resolveChainFix(makeFinding(), new Map(), "npm", { + offline: false, + }); expect(result).toBeNull(); }); it("returns null when fetchPackument returns 404 for direct dep", async () => { - fetchMock.mockResolvedValue({ ok: false, status: 404, json: async () => ({}) }); + fetchMock.mockResolvedValue({ + ok: false, + status: 404, + json: async () => ({}), + }); const result = await resolveChainFix( makeFinding(), new Map([["express", "4.17.0"]]), @@ -132,7 +133,11 @@ describe("resolveChainFix", () => { }), }; } - if (urlStr.endsWith("/send") || urlStr.includes("/send?") || urlStr.includes("registry.npmjs.org/send")) { + if ( + urlStr.endsWith("/send") || + urlStr.includes("/send?") || + urlStr.includes("registry.npmjs.org/send") + ) { return { ok: true, json: async () => ({ diff --git a/tests/update-check.test.ts b/tests/update-check.test.ts index c5df235e..c8cd3586 100644 --- a/tests/update-check.test.ts +++ b/tests/update-check.test.ts @@ -2,7 +2,12 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { jest } from "@jest/globals"; -import { isNewer, readCache, writeCache, isCacheStale } from "../src/utils/update-check.js"; +import { + isNewer, + readCache, + writeCache, + isCacheStale, +} from "../src/utils/update-check.js"; import { removeDir } from "./test-utils.js"; describe("isNewer", () => { @@ -57,22 +62,37 @@ describe("readCache", () => { it("returns null for a corrupt cache file", () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cve-test-")); - fs.writeFileSync(path.join(tmpDir, "update-check.json"), "not-json", "utf8"); + fs.writeFileSync( + path.join(tmpDir, "update-check.json"), + "not-json", + "utf8", + ); expect(readCache(tmpDir)).toBeNull(); removeDir(tmpDir); }); it("returns null when required fields are missing", () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cve-test-")); - fs.writeFileSync(path.join(tmpDir, "update-check.json"), JSON.stringify({ foo: "bar" }), "utf8"); + fs.writeFileSync( + path.join(tmpDir, "update-check.json"), + JSON.stringify({ foo: "bar" }), + "utf8", + ); expect(readCache(tmpDir)).toBeNull(); removeDir(tmpDir); }); it("returns the cache when file is valid", () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cve-test-")); - const payload = { latestVersion: "1.18.0", checkedAt: new Date().toISOString() }; - fs.writeFileSync(path.join(tmpDir, "update-check.json"), JSON.stringify(payload), "utf8"); + const payload = { + latestVersion: "1.18.0", + checkedAt: new Date().toISOString(), + }; + fs.writeFileSync( + path.join(tmpDir, "update-check.json"), + JSON.stringify(payload), + "utf8", + ); expect(readCache(tmpDir)).toEqual(payload); removeDir(tmpDir); }); @@ -91,13 +111,18 @@ describe("writeCache", () => { describe("isCacheStale", () => { it("returns false for a cache written now", () => { - const cache = { latestVersion: "1.18.0", checkedAt: new Date().toISOString() }; + const cache = { + latestVersion: "1.18.0", + checkedAt: new Date().toISOString(), + }; expect(isCacheStale(cache)).toBe(false); }); it("returns true for a cache written 25 hours ago", () => { const old = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(); - expect(isCacheStale({ latestVersion: "1.18.0", checkedAt: old })).toBe(true); + expect(isCacheStale({ latestVersion: "1.18.0", checkedAt: old })).toBe( + true, + ); }); }); @@ -123,7 +148,8 @@ describe("checkForUpdate", () => { removeDir(tmpDir); if (savedCI !== undefined) process.env["CI"] = savedCI; else delete process.env["CI"]; - if (savedNoUpdateNotifier !== undefined) process.env["NO_UPDATE_NOTIFIER"] = savedNoUpdateNotifier; + if (savedNoUpdateNotifier !== undefined) + process.env["NO_UPDATE_NOTIFIER"] = savedNoUpdateNotifier; else delete process.env["NO_UPDATE_NOTIFIER"]; }); @@ -131,7 +157,7 @@ describe("checkForUpdate", () => { writeCache("999.0.0", tmpDir); checkForUpdate({ cacheDir: tmpDir }); expect(consoleSpy).toHaveBeenCalled(); - const output = consoleSpy.mock.calls.map(c => String(c[0])).join("\n"); + const output = consoleSpy.mock.calls.map((c) => String(c[0])).join("\n"); expect(output).toContain("Update available!"); expect(output).toContain("999.0.0"); expect(output).toContain("npm install -g cve-lite-cli"); @@ -183,7 +209,11 @@ describe("checkForUpdate", () => { }); it("does not throw when the cache file is corrupt", () => { - fs.writeFileSync(path.join(tmpDir, "update-check.json"), "not-json", "utf8"); + fs.writeFileSync( + path.join(tmpDir, "update-check.json"), + "not-json", + "utf8", + ); expect(() => checkForUpdate({ cacheDir: tmpDir })).not.toThrow(); expect(consoleSpy).not.toHaveBeenCalled(); }); diff --git a/tests/usage.test.ts b/tests/usage.test.ts index 9ec3a778..fea67c56 100644 --- a/tests/usage.test.ts +++ b/tests/usage.test.ts @@ -22,21 +22,34 @@ describe("scanProjectForPackageUsage", () => { } it("should find imports and requires", () => { - createTestFile("src/index.js", ` + createTestFile( + "src/index.js", + ` import { someMethod } from 'lodash'; import * as utils from "my-utils"; const express = require('express'); await import('dynamic-pkg'); export { something } from 'exported-pkg'; - `); + `, + ); - createTestFile("src/other.ts", ` + createTestFile( + "src/other.ts", + ` import 'side-effect-pkg'; import type { Foo } from '@scope/types'; - `); + `, + ); const packagesToLookFor = new Set([ - "lodash", "my-utils", "express", "dynamic-pkg", "exported-pkg", "side-effect-pkg", "@scope/types", "not-found-pkg" + "lodash", + "my-utils", + "express", + "dynamic-pkg", + "exported-pkg", + "side-effect-pkg", + "@scope/types", + "not-found-pkg", ]); const results = scanProjectForPackageUsage(tempDir, packagesToLookFor); @@ -48,10 +61,10 @@ describe("scanProjectForPackageUsage", () => { expect(results["express"].length).toBe(1); expect(results["dynamic-pkg"].length).toBe(1); expect(results["exported-pkg"].length).toBe(1); - + expect(results["side-effect-pkg"].length).toBe(1); expect(results["side-effect-pkg"][0]).toMatch(/src.other\.ts/); - + expect(results["@scope/types"].length).toBe(1); expect(results["not-found-pkg"].length).toBe(0); @@ -65,19 +78,22 @@ describe("scanProjectForPackageUsage", () => { createTestFile("src/index.js", "import 'lodash';"); const results = scanProjectForPackageUsage(tempDir, new Set(["lodash"])); - + expect(results["lodash"].length).toBe(1); expect(results["lodash"][0]).toMatch(/src.index\.js/); }); it("should extract bare module names correctly", () => { - createTestFile("src/index.js", ` + createTestFile( + "src/index.js", + ` import { x } from 'lodash/fp/map'; require('@scope/pkg/submodule/file.js'); import './local-file.js'; import '../parent.js'; import '/absolute/path.js'; - `); + `, + ); const packages = new Set(["lodash", "@scope/pkg", "local-file"]); const results = scanProjectForPackageUsage(tempDir, packages); diff --git a/tests/validate.test.ts b/tests/validate.test.ts index 174de57c..de7b33d4 100644 --- a/tests/validate.test.ts +++ b/tests/validate.test.ts @@ -8,15 +8,17 @@ function opts(overrides: Partial = {}): ParsedOptions { describe("validateOptions", () => { describe("--offline / --offline-db with --osv-url", () => { it("throws when --offline and --osv-url are combined", () => { - expect(() => validateOptions(opts({ offline: true, osvUrl: "https://example.com" }))).toThrow( - "--offline/--offline-db cannot be used with --osv-url", - ); + expect(() => + validateOptions(opts({ offline: true, osvUrl: "https://example.com" })), + ).toThrow("--offline/--offline-db cannot be used with --osv-url"); }); it("throws when --offline-db and --osv-url are combined", () => { - expect(() => validateOptions(opts({ offlineDb: "/path/to/db", osvUrl: "https://example.com" }))).toThrow( - "--offline/--offline-db cannot be used with --osv-url", - ); + expect(() => + validateOptions( + opts({ offlineDb: "/path/to/db", osvUrl: "https://example.com" }), + ), + ).toThrow("--offline/--offline-db cannot be used with --osv-url"); }); it("does not throw when --offline is used without --osv-url", () => { @@ -26,15 +28,15 @@ describe("validateOptions", () => { describe("--no-cache with --offline / --offline-db", () => { it("throws when --no-cache and --offline are combined", () => { - expect(() => validateOptions(opts({ noCache: true, offline: true }))).toThrow( - "--no-cache cannot be used with --offline or --offline-db", - ); + expect(() => + validateOptions(opts({ noCache: true, offline: true })), + ).toThrow("--no-cache cannot be used with --offline or --offline-db"); }); it("throws when --no-cache and --offline-db are combined", () => { - expect(() => validateOptions(opts({ noCache: true, offlineDb: "/path/to/db" }))).toThrow( - "--no-cache cannot be used with --offline or --offline-db", - ); + expect(() => + validateOptions(opts({ noCache: true, offlineDb: "/path/to/db" })), + ).toThrow("--no-cache cannot be used with --offline or --offline-db"); }); it("does not throw when --no-cache is used without offline flags", () => { @@ -50,7 +52,9 @@ describe("validateOptions", () => { }); it("does not throw when --osv-url is a valid URL", () => { - expect(() => validateOptions(opts({ osvUrl: "https://custom.osv.example.com" }))).not.toThrow(); + expect(() => + validateOptions(opts({ osvUrl: "https://custom.osv.example.com" })), + ).not.toThrow(); }); }); @@ -68,21 +72,23 @@ describe("validateOptions", () => { describe("--report with --json", () => { it("throws when --report and --json are combined", () => { - expect(() => validateOptions(opts({ report: "report.html", json: true }))).toThrow( - "--report cannot be used with --json", - ); + expect(() => + validateOptions(opts({ report: "report.html", json: true })), + ).toThrow("--report cannot be used with --json"); }); it("does not throw when --report is used without --json", () => { - expect(() => validateOptions(opts({ report: "report.html" }))).not.toThrow(); + expect(() => + validateOptions(opts({ report: "report.html" })), + ).not.toThrow(); }); }); describe("--ca-cert validation", () => { it("throws with a --ca-cert: prefix when the cert file does not exist or is invalid", () => { - expect(() => validateOptions(opts({ caCert: "invalid-fake-cert.pem" }))).toThrow( - "--ca-cert:" - ); + expect(() => + validateOptions(opts({ caCert: "invalid-fake-cert.pem" })), + ).toThrow("--ca-cert:"); }); it("does not throw when --ca-cert is not set", () => { @@ -91,6 +97,8 @@ describe("validateOptions", () => { }); it("does not throw for a valid set of options", () => { - expect(() => validateOptions(opts({ fix: true, verbose: true }))).not.toThrow(); + expect(() => + validateOptions(opts({ fix: true, verbose: true })), + ).not.toThrow(); }); }); diff --git a/tests/write-outputs.test.ts b/tests/write-outputs.test.ts index 95361bd6..9f0152b7 100644 --- a/tests/write-outputs.test.ts +++ b/tests/write-outputs.test.ts @@ -1,15 +1,24 @@ import { jest } from "@jest/globals"; -import type { Finding, PackageRef, ParsedOptions, ScanInput } from "../src/types.js"; +import type { + Finding, + PackageRef, + ParsedOptions, + ScanInput, +} from "../src/types.js"; import type { ScanState } from "../src/output/write-outputs.js"; const writeFileSyncMock = jest.fn(); const existsSyncMock = jest.fn(() => false); -const readFileSyncMock = jest.fn(() => JSON.stringify({ name: "my-project", version: "1.0.0" })); +const readFileSyncMock = jest.fn(() => + JSON.stringify({ name: "my-project", version: "1.0.0" }), +); const writeSarifReportMock = jest.fn(() => "cve-lite-scan-test.sarif"); const deriveLockfileUriMock = jest.fn(() => "package-lock.json"); -const writeCycloneDxReportMock = jest.fn(() => "cve-lite-scan-test.cdx.json"); +const writeCycloneDxReportMock = jest.fn( + () => "cve-lite-scan-test.cdx.json", +); jest.unstable_mockModule("node:fs", () => ({ default: { @@ -47,7 +56,9 @@ beforeEach(() => { jest.clearAllMocks(); writeSarifReportMock.mockReturnValue("cve-lite-scan-test.sarif"); writeCycloneDxReportMock.mockReturnValue("cve-lite-scan-test.cdx.json"); - readFileSyncMock.mockReturnValue(JSON.stringify({ name: "my-project", version: "1.0.0" })); + readFileSyncMock.mockReturnValue( + JSON.stringify({ name: "my-project", version: "1.0.0" }), + ); existsSyncMock.mockReturnValue(true); }); @@ -86,17 +97,32 @@ describe("writeOutputs", () => { }); it("calls writeSarifReport when sarif: true", async () => { - await writeOutputs(makeOptions({ sarif: true }), mockScanState, mockScanInput, "/tmp/project"); + await writeOutputs( + makeOptions({ sarif: true }), + mockScanState, + mockScanInput, + "/tmp/project", + ); expect(writeSarifReportMock).toHaveBeenCalledTimes(1); }); it("calls writeCycloneDxReport when cdx: true", async () => { - await writeOutputs(makeOptions({ cdx: true }), mockScanState, mockScanInput, "/tmp/project"); + await writeOutputs( + makeOptions({ cdx: true }), + mockScanState, + mockScanInput, + "/tmp/project", + ); expect(writeCycloneDxReportMock).toHaveBeenCalledTimes(1); }); it("calls writeFileSync when json: true", async () => { - await writeOutputs(makeOptions({ json: true }), mockScanState, mockScanInput, "/tmp/project"); + await writeOutputs( + makeOptions({ json: true }), + mockScanState, + mockScanInput, + "/tmp/project", + ); expect(writeFileSyncMock).toHaveBeenCalledTimes(1); const [, jsonContent] = writeFileSyncMock.mock.calls[0] as [string, string]; const parsed = JSON.parse(jsonContent); @@ -111,43 +137,80 @@ describe("writeOutputs", () => { }); it("logs the saved filename to stdout when json: true", async () => { - await writeOutputs(makeOptions({ json: true }), mockScanState, mockScanInput, "/tmp/project"); - const logged = consoleSpy.mock.calls.map(call => call.join(" ")).join("\n"); + await writeOutputs( + makeOptions({ json: true }), + mockScanState, + mockScanInput, + "/tmp/project", + ); + const logged = consoleSpy.mock.calls + .map((call) => call.join(" ")) + .join("\n"); expect(logged).toContain("JSON saved to"); expect(logged).toMatch(/cve-lite-scan-.*\.json/); }); it("does NOT call writeSarifReport when sarif is not set", async () => { - await writeOutputs(makeOptions(), mockScanState, mockScanInput, "/tmp/project"); + await writeOutputs( + makeOptions(), + mockScanState, + mockScanInput, + "/tmp/project", + ); expect(writeSarifReportMock).not.toHaveBeenCalled(); }); it("does NOT call writeCycloneDxReport when cdx is not set", async () => { - await writeOutputs(makeOptions(), mockScanState, mockScanInput, "/tmp/project"); + await writeOutputs( + makeOptions(), + mockScanState, + mockScanInput, + "/tmp/project", + ); expect(writeCycloneDxReportMock).not.toHaveBeenCalled(); }); describe("readProjectMeta (via cdx path)", () => { it("passes null projectMeta when package.json does not exist", async () => { existsSyncMock.mockReturnValueOnce(false); - await writeOutputs(makeOptions({ cdx: true }), mockScanState, mockScanInput, "/project"); - const [,, planArg, metaArg] = writeCycloneDxReportMock.mock.calls[0] as any[]; + await writeOutputs( + makeOptions({ cdx: true }), + mockScanState, + mockScanInput, + "/project", + ); + const [, , planArg, metaArg] = writeCycloneDxReportMock.mock + .calls[0] as any[]; expect(metaArg).toBeNull(); }); it("passes null projectMeta when package.json has invalid JSON", async () => { existsSyncMock.mockReturnValueOnce(true); readFileSyncMock.mockReturnValueOnce("not valid json"); - await writeOutputs(makeOptions({ cdx: true }), mockScanState, mockScanInput, "/project"); - const [,, planArg, metaArg] = writeCycloneDxReportMock.mock.calls[0] as any[]; + await writeOutputs( + makeOptions({ cdx: true }), + mockScanState, + mockScanInput, + "/project", + ); + const [, , planArg, metaArg] = writeCycloneDxReportMock.mock + .calls[0] as any[]; expect(metaArg).toBeNull(); }); it("passes null projectMeta when package.json has no name field", async () => { existsSyncMock.mockReturnValueOnce(true); - readFileSyncMock.mockReturnValueOnce(JSON.stringify({ version: "1.0.0" })); - await writeOutputs(makeOptions({ cdx: true }), mockScanState, mockScanInput, "/project"); - const [,, planArg, metaArg] = writeCycloneDxReportMock.mock.calls[0] as any[]; + readFileSyncMock.mockReturnValueOnce( + JSON.stringify({ version: "1.0.0" }), + ); + await writeOutputs( + makeOptions({ cdx: true }), + mockScanState, + mockScanInput, + "/project", + ); + const [, , planArg, metaArg] = writeCycloneDxReportMock.mock + .calls[0] as any[]; expect(metaArg).toBeNull(); }); }); diff --git a/tsconfig.json b/tsconfig.json index 026a5831..30a2d26c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,8 +10,5 @@ "esModuleInterop": true, "resolveJsonModule": true }, - "include": [ - "src/**/*.ts", - "src/**/*.d.ts" - ] -} \ No newline at end of file + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/tsconfig.test.json b/tsconfig.test.json index d835e0a7..c6fcab75 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -6,9 +6,5 @@ "rootDir": ".", "types": ["node", "jest"] }, - "include": [ - "src/**/*.ts", - "src/**/*.d.ts", - "tests/**/*.ts" - ] + "include": ["src/**/*.ts", "src/**/*.d.ts", "tests/**/*.ts"] } diff --git a/website/docs/ai-assistant-integration.md b/website/docs/ai-assistant-integration.md index d991edff..388b073c 100644 --- a/website/docs/ai-assistant-integration.md +++ b/website/docs/ai-assistant-integration.md @@ -32,13 +32,13 @@ Running it twice is safe — existing sections are replaced in place, surroundin ### What gets installed -| Tool | File | Behavior | -|------|------|----------| -| Claude Code | `.claude/commands/cve-lite.md` | Created or overwritten | -| Codex CLI | `AGENTS.md` | `## CVE Lite CLI` section written | -| Gemini CLI | `GEMINI.md` | `## CVE Lite CLI` section written | -| Cursor | `.cursor/rules/cve-lite.mdc` | Created or overwritten with front-matter | -| GitHub Copilot | `.github/copilot-instructions.md` | `## CVE Lite CLI` section written | +| Tool | File | Behavior | +| -------------- | --------------------------------- | ---------------------------------------- | +| Claude Code | `.claude/commands/cve-lite.md` | Created or overwritten | +| Codex CLI | `AGENTS.md` | `## CVE Lite CLI` section written | +| Gemini CLI | `GEMINI.md` | `## CVE Lite CLI` section written | +| Cursor | `.cursor/rules/cve-lite.mdc` | Created or overwritten with front-matter | +| GitHub Copilot | `.github/copilot-instructions.md` | `## CVE Lite CLI` section written | Commit these files to your repository. Every developer who clones the repo gets the skills automatically. @@ -61,6 +61,7 @@ The scan results are saved to a timestamped file (`cve-lite-scan-.jso In Claude Code, invoke the skill with `/cve-lite`. In other tools the skill is picked up automatically when you ask about vulnerabilities. The assistant will: + - Read the JSON output - Prioritize findings by severity and relationship (direct before transitive) - Check whether vulnerable packages are actually imported in your source code diff --git a/website/docs/caching.md b/website/docs/caching.md index ad465519..a0e14722 100644 --- a/website/docs/caching.md +++ b/website/docs/caching.md @@ -12,10 +12,10 @@ CVE Lite CLI caches OSV advisory results locally so that repeated scans — comm Two separate caches share a single file at `~/.cache/cve-lite/osv-vulns.json`: -| Cache | Key | Value | TTL | -|---|---|---|---| -| **Query cache** (`queryEntries`) | `ecosystem:package@version` | List of matching advisory IDs | 30 minutes | -| **Advisory detail cache** (`entries`) | Advisory ID (e.g. `GHSA-...`) | Full advisory record | No expiry | +| Cache | Key | Value | TTL | +| ------------------------------------- | ----------------------------- | ----------------------------- | ---------- | +| **Query cache** (`queryEntries`) | `ecosystem:package@version` | List of matching advisory IDs | 30 minutes | +| **Advisory detail cache** (`entries`) | Advisory ID (e.g. `GHSA-...`) | Full advisory record | No expiry | The **query cache** is the one that affects scan results. It records which advisories matched a given package version. After 30 minutes, any entry in this cache is treated as stale and re-queried from OSV — regardless of whether the previous result was empty or not. diff --git a/website/docs/case-studies/analog.md b/website/docs/case-studies/analog.md index a85ceba0..26eab66a 100644 --- a/website/docs/case-studies/analog.md +++ b/website/docs/case-studies/analog.md @@ -33,18 +33,18 @@ The most instructive chains here are not obvious ones. `@compodoc/compodoc` — Both tools were run against the same `pnpm-lock.yaml` on the same machine. -| Metric | pnpm audit | CVE Lite CLI v1.6.0 | -|---|---:|---:| -| Total reported findings | 85 | 37 | -| Critical | 1 | 1 | -| High | 34 | 19 | -| Moderate / Medium | 47 | 16 | -| Low | 3 | 1 | -| Direct vs transitive breakdown | ✗ | ✓ (5 / 32) | -| Validated fix targets | ✗ | ✓ | -| Breaking change awareness | ✗ | ✓ | -| Parent chain identified for transitive issues | ✗ | ✓ | -| Specific copy-and-run commands | ✗ | ✓ | +| Metric | pnpm audit | CVE Lite CLI v1.6.0 | +| --------------------------------------------- | ---------: | ------------------: | +| Total reported findings | 85 | 37 | +| Critical | 1 | 1 | +| High | 34 | 19 | +| Moderate / Medium | 47 | 16 | +| Low | 3 | 1 | +| Direct vs transitive breakdown | ✗ | ✓ (5 / 32) | +| Validated fix targets | ✗ | ✓ | +| Breaking change awareness | ✗ | ✓ | +| Parent chain identified for transitive issues | ✗ | ✓ | +| Specific copy-and-run commands | ✗ | ✓ | **Why CVE Lite reports fewer findings — and why that is not a coverage gap:** @@ -82,12 +82,12 @@ Each command is a validated non-vulnerable stable target. `pnpm audit --fix` mar Remediation results from applying the three command groups documented in this study, measured one group at a time: -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline | 37 | 1 | 19 | 16 | 1 | 5 | 32 | 3 | -| After high severity direct fixes | 33 | 1 | 15 | 16 | 1 | 1 | 32 | 3 | -| After medium severity direct fix | 32 | 1 | 15 | 15 | 1 | 0 | 32 | 2 | -| After medium severity parent upgrades | 31 | 1 | 15 | 14 | 1 | 0 | 31 | 2 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline | 37 | 1 | 19 | 16 | 1 | 5 | 32 | 3 | +| After high severity direct fixes | 33 | 1 | 15 | 16 | 1 | 1 | 32 | 3 | +| After medium severity direct fix | 32 | 1 | 15 | 15 | 1 | 0 | 32 | 2 | +| After medium severity parent upgrades | 31 | 1 | 15 | 14 | 1 | 0 | 31 | 2 | The finding count dropped from 37 to 31. High-severity findings dropped from 19 to 15. The direct package surface cleared entirely from 5 to 0 across two passes — no remaining packages the project controls directly have an unaddressed upgrade target. The command surface dropped from 3 groups to 2, and those 2 are parent-chain moves rather than direct installs. The scanner moved the project cleanly out of the confident first-pass tier and into the structural transitive tier after three targeted command groups. @@ -154,44 +154,44 @@ These require tooling upgrades, dependency replacements, or extended parent-chai Full vulnerable package list at scan time: -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| handlebars | 4.7.8 | critical | transitive | 4.7.9 | GHSA-q42p-pg8m-cqh6, GHSA-442j-39wm-28r2 | -| @angular/platform-server | 21.2.7 | high | direct | 21.2.9 | GHSA-5pq3-h73f-66hr | -| defu | 6.1.4 | high | direct | 6.1.5 | GHSA-737v-mqg7-c878 | -| flatted | 3.4.1 | high | transitive | 3.4.2 | GHSA-rf6f-7fwh-wjgh | -| happy-dom | 20.8.4 | high | direct | 20.8.8 | GHSA-6q6h-j7hj-3r64, GHSA-w4gp-fjgq-3q4g | -| hono | 4.11.5 | high | transitive | 4.11.7 | GHSA-26pp-8wgv-hjvm, GHSA-458j-xx4x-437… | -| lodash-es | 4.17.23 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | -| lodash | 4.17.21 | high | transitive | 4.17.23 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | -| lodash | 4.17.23 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | -| minimatch | 3.0.8 | high | transitive | 3.1.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2… | -| node-forge | 1.3.3 | high | transitive | 1.4.0 | GHSA-2328-f5f3-gj25, GHSA-5m6q-g25r-mvw… | -| path-to-regexp | 0.1.12 | high | transitive | 0.1.13 | GHSA-37ch-88jc-xwx2 | -| path-to-regexp | 1.8.0 | high | transitive | 0.1.10 | GHSA-9wv6-86v2-598j | -| path-to-regexp | 8.3.0 | high | transitive | 8.4.0 | GHSA-27v5-c462-wpq7, GHSA-j3q9-mxjg-w52f | -| picomatch | 2.3.1 | high | transitive | 2.3.2 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | -| picomatch | 4.0.3 | high | transitive | 2.3.2 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | -| svgo | 3.3.2 | high | transitive | 2.8.1 | GHSA-xpqw-6gx7-v673 | -| vite | 8.0.0 | high | direct | 6.4.2 | GHSA-4w7w-66w2-5vf9, GHSA-p9ff-h696-f58… | -| @hono/node-server | 1.19.11 | medium | transitive | 1.19.13 | GHSA-92pp-h63x-v22m | -| ajv | 8.17.1 | medium | transitive | 6.14.0 | GHSA-2g4f-4pwh-qvx6 | -| axios | 1.13.6 | medium | transitive | 0.31.0 | GHSA-3p68-rc4w-qgx5, GHSA-fvcv-3m26-pcqx | -| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | -| brace-expansion | 2.0.2 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | -| brace-expansion | 5.0.4 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | -| dompurify | 3.3.3 | medium | transitive | 3.4.0 | GHSA-39q2-94rc-95cp | -| estree-util-value-to-estree | 3.1.1 | medium | transitive | 3.3.3 | GHSA-f7f6-9jq7-3rqj | -| file-type | 20.5.0 | medium | transitive | 21.3.1 | GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v | -| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | -| h3 | 1.15.6 | medium | direct | 1.15.9 | GHSA-4hxc-9384-m385, GHSA-72gr-qfp7-vwhw | -| mdast-util-to-hast | 13.1.0 | medium | transitive | 13.2.1 | GHSA-4fh9-h7wg-q85m | -| serialize-javascript | 7.0.4 | medium | transitive | 7.0.5 | GHSA-qj8w-gfj5-8c6v | -| smol-toml | 1.6.0 | medium | transitive | 1.6.1 | GHSA-v3rj-xjv7-4jmq | -| yaml | 1.10.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | -| yaml | 2.8.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | -| @angular/cli | 21.2.6 | low | direct | — | — | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| --------------------------- | ------- | -------- | ------------ | -------- | ---------------------------------------- | +| handlebars | 4.7.8 | critical | transitive | 4.7.9 | GHSA-q42p-pg8m-cqh6, GHSA-442j-39wm-28r2 | +| @angular/platform-server | 21.2.7 | high | direct | 21.2.9 | GHSA-5pq3-h73f-66hr | +| defu | 6.1.4 | high | direct | 6.1.5 | GHSA-737v-mqg7-c878 | +| flatted | 3.4.1 | high | transitive | 3.4.2 | GHSA-rf6f-7fwh-wjgh | +| happy-dom | 20.8.4 | high | direct | 20.8.8 | GHSA-6q6h-j7hj-3r64, GHSA-w4gp-fjgq-3q4g | +| hono | 4.11.5 | high | transitive | 4.11.7 | GHSA-26pp-8wgv-hjvm, GHSA-458j-xx4x-437… | +| lodash-es | 4.17.23 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | +| lodash | 4.17.21 | high | transitive | 4.17.23 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | +| lodash | 4.17.23 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | +| minimatch | 3.0.8 | high | transitive | 3.1.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2… | +| node-forge | 1.3.3 | high | transitive | 1.4.0 | GHSA-2328-f5f3-gj25, GHSA-5m6q-g25r-mvw… | +| path-to-regexp | 0.1.12 | high | transitive | 0.1.13 | GHSA-37ch-88jc-xwx2 | +| path-to-regexp | 1.8.0 | high | transitive | 0.1.10 | GHSA-9wv6-86v2-598j | +| path-to-regexp | 8.3.0 | high | transitive | 8.4.0 | GHSA-27v5-c462-wpq7, GHSA-j3q9-mxjg-w52f | +| picomatch | 2.3.1 | high | transitive | 2.3.2 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | +| picomatch | 4.0.3 | high | transitive | 2.3.2 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | +| svgo | 3.3.2 | high | transitive | 2.8.1 | GHSA-xpqw-6gx7-v673 | +| vite | 8.0.0 | high | direct | 6.4.2 | GHSA-4w7w-66w2-5vf9, GHSA-p9ff-h696-f58… | +| @hono/node-server | 1.19.11 | medium | transitive | 1.19.13 | GHSA-92pp-h63x-v22m | +| ajv | 8.17.1 | medium | transitive | 6.14.0 | GHSA-2g4f-4pwh-qvx6 | +| axios | 1.13.6 | medium | transitive | 0.31.0 | GHSA-3p68-rc4w-qgx5, GHSA-fvcv-3m26-pcqx | +| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | +| brace-expansion | 2.0.2 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | +| brace-expansion | 5.0.4 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | +| dompurify | 3.3.3 | medium | transitive | 3.4.0 | GHSA-39q2-94rc-95cp | +| estree-util-value-to-estree | 3.1.1 | medium | transitive | 3.3.3 | GHSA-f7f6-9jq7-3rqj | +| file-type | 20.5.0 | medium | transitive | 21.3.1 | GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v | +| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | +| h3 | 1.15.6 | medium | direct | 1.15.9 | GHSA-4hxc-9384-m385, GHSA-72gr-qfp7-vwhw | +| mdast-util-to-hast | 13.1.0 | medium | transitive | 13.2.1 | GHSA-4fh9-h7wg-q85m | +| serialize-javascript | 7.0.4 | medium | transitive | 7.0.5 | GHSA-qj8w-gfj5-8c6v | +| smol-toml | 1.6.0 | medium | transitive | 1.6.1 | GHSA-v3rj-xjv7-4jmq | +| yaml | 1.10.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | +| yaml | 2.8.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | +| @angular/cli | 21.2.6 | low | direct | — | — | --- diff --git a/website/docs/case-studies/astro.md b/website/docs/case-studies/astro.md index 7c7aa1f7..b3b38e72 100644 --- a/website/docs/case-studies/astro.md +++ b/website/docs/case-studies/astro.md @@ -43,19 +43,19 @@ pnpm add esbuild@0.25.0 turbo@2.9.14 Both tools were run against the same `pnpm-lock.yaml` on the same machine on 2026-05-26. -| Metric | pnpm audit (11.0.9) | CVE Lite CLI v1.18.0 | -|---|---:|---:| -| Total reported findings | 77 | 34 | -| Critical | 1 | 1 | -| High | 34 | 13 | -| Moderate / Medium | 37 | 19 | -| Low | 5 | 1 | -| Direct vs transitive breakdown | ✗ | ✓ (2 / 32) | -| Deduplicated package view | ✗ | ✓ | -| Validated fix targets | ✗ | ✓ | -| `pnpm update` vs parent upgrade distinction | ✗ | ✓ | -| Specific copy-and-run commands | ✗ | ✓ (4 groups) | -| Skipped findings with reason | ✗ | ✓ (29 entries) | +| Metric | pnpm audit (11.0.9) | CVE Lite CLI v1.18.0 | +| ------------------------------------------- | ------------------: | -------------------: | +| Total reported findings | 77 | 34 | +| Critical | 1 | 1 | +| High | 34 | 13 | +| Moderate / Medium | 37 | 19 | +| Low | 5 | 1 | +| Direct vs transitive breakdown | ✗ | ✓ (2 / 32) | +| Deduplicated package view | ✗ | ✓ | +| Validated fix targets | ✗ | ✓ | +| `pnpm update` vs parent upgrade distinction | ✗ | ✓ | +| Specific copy-and-run commands | ✗ | ✓ (4 groups) | +| Skipped findings with reason | ✗ | ✓ (29 entries) | **Why the totals differ — and why that is not a coverage gap:** @@ -86,9 +86,9 @@ For the critical finding, CVE Lite names `@flue/sdk` as the parent chain (`proje No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 34 | 1 | 13 | 19 | 1 | 2 | 32 | 4 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 34 | 1 | 13 | 19 | 1 | 2 | 32 | 4 | The first-pass plan covers **5 of 34** findings. The remaining **29** appear in the skipped section with explicit reasons — usually "no safe parent upgrade identified automatically" for transitive packages like `@flue/sdk`, `mocha`, `@vscode/test-cli`, and `@netlify/vite-plugin`. @@ -148,19 +148,19 @@ The example lockfile in this repository reflects Astro at revision `221bb4b36831 Every number in this case study comes from a live scan of the committed fixture at `examples/astro/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-05-26 | -| CLI version | v1.18.0 | -| CVE Lite command | `npx tsx src/index.ts examples/astro --json --all` | -| pnpm audit command | `pnpm audit` (requires Node.js ≥ 22.13 for pnpm 11.0.9) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/astro/pnpm-lock.yaml` from [withastro/astro@221bb4b](https://github.com/withastro/astro/commit/221bb4b36831f3fc278f05dc40a7498abb864ddf) | -| Packages parsed (CVE Lite) | 2,228 | -| Unique vulnerable packages (CVE Lite) | 34 | -| Vulnerability entries (pnpm audit) | 77 | -| Fix command groups (CVE Lite) | 4 | -| Skipped findings with reason (CVE Lite) | 29 | +| Field | Value | +| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-05-26 | +| CLI version | v1.18.0 | +| CVE Lite command | `npx tsx src/index.ts examples/astro --json --all` | +| pnpm audit command | `pnpm audit` (requires Node.js ≥ 22.13 for pnpm 11.0.9) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/astro/pnpm-lock.yaml` from [withastro/astro@221bb4b](https://github.com/withastro/astro/commit/221bb4b36831f3fc278f05dc40a7498abb864ddf) | +| Packages parsed (CVE Lite) | 2,228 | +| Unique vulnerable packages (CVE Lite) | 34 | +| Vulnerability entries (pnpm audit) | 77 | +| Fix command groups (CVE Lite) | 4 | +| Skipped findings with reason (CVE Lite) | 29 | Reproduce CVE Lite locally from the repository root: @@ -196,42 +196,42 @@ All 34 baseline findings remain open at the time of this study. No remediation w Full vulnerable package list from the verified scan on 2026-05-26 (revision `221bb4b`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| fast-xml-parser | 5.3.3 | critical | transitive | 5.7.0 | GHSA-37qj-frw5-hhjh, GHSA-8gc5-j5rx-235r | -| @xmldom/xmldom | 0.9.8 | high | transitive | 0.9.10 | GHSA-2v35-w6hq-6mfw, GHSA-f6ww-3ggp-fr8h | -| axios | 1.13.5 | high | transitive | 1.15.2 | GHSA-3p68-rc4w-qgx5, GHSA-3w6x-2g7m-8v23 | -| devalue | 5.6.4 | high | transitive | 5.8.1 | GHSA-77vg-94rm-hx3p | -| fast-uri | 3.1.0 | high | transitive | 3.1.2 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | -| fast-xml-builder | 1.1.5 | high | transitive | 1.1.7 | GHSA-45c6-75p6-83cc, GHSA-5wm8-gmm8-39j9 | -| flatted | 3.3.3 | high | transitive | 3.4.2 | GHSA-25h7-pfq9-p65f, GHSA-rf6f-7fwh-wjgh | -| minimatch | 9.0.5 | high | transitive | 9.0.7 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 3.1.2 | high | transitive | 3.1.4 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 5.1.6 | high | transitive | 5.1.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| node-forge | 1.3.3 | high | transitive | 1.4.0 | GHSA-2328-f5f3-gj25, GHSA-5m6q-g25r-mvwx | -| path-to-regexp | 6.1.0 | high | transitive | 6.3.0 | GHSA-9wv6-86v2-598j | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | -| underscore | 1.13.7 | high | transitive | 1.13.8 | GHSA-qpx9-hpmf-5gmw | -| @fastify/static | 9.0.0 | medium | transitive | 9.1.1 | GHSA-pr96-94w5-mx2h, GHSA-x428-ghpx-8j92 | -| ajv | 6.12.6 | medium | transitive | 6.14.0 | GHSA-2g4f-4pwh-qvx6 | -| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | -| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | GHSA-f886-m6hf-6m8v | -| brace-expansion | 5.0.2 | medium | transitive | 5.0.6 | GHSA-f886-m6hf-6m8v, GHSA-jxxr-4gwj-5jf2 | -| esbuild | 0.17.19 | medium | direct | 0.25.0 | GHSA-67mh-4wv8-2f99 | -| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | -| hono | 4.12.16 | medium | transitive | 4.12.18 | GHSA-hm8q-7f3q-5f36, GHSA-p77w-8qqv-26rm | -| ip-address | 10.1.0 | medium | transitive | 10.1.1 | GHSA-v2v4-37r5-5v8g | -| protobufjs | 7.5.6 | medium | transitive | 7.5.8 | GHSA-jggg-4jg4-v7c6 | -| qs | 6.14.2 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | -| svelte | 5.55.3 | medium | transitive | 5.55.7 | GHSA-9rmh-mm8f-r9h6, GHSA-f3cj-j4f6-wq85 | -| turbo | 2.8.15 | medium | direct | 2.9.14 | GHSA-3qcw-2rhx-2726, GHSA-hcf7-66rw-9f5r | -| uuid | 8.3.2 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | -| uuid | 11.1.0 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | -| uuid | 13.0.0 | medium | transitive | 13.0.1 | GHSA-w5hq-g745-h8pq | -| ws | 8.18.0 | medium | transitive | 8.20.1 | GHSA-58qx-3vcg-4xpx | -| ws | 8.19.0 | medium | transitive | 8.20.1 | GHSA-58qx-3vcg-4xpx | -| yaml | 2.7.1 | medium | transitive | 2.8.3 | GHSA-48c2-rrv3-qjmp | -| diff | 7.0.0 | low | transitive | 8.0.3 | GHSA-73rr-hh4g-fpgx | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| -------------------- | ------- | -------- | ------------ | -------- | ---------------------------------------- | +| fast-xml-parser | 5.3.3 | critical | transitive | 5.7.0 | GHSA-37qj-frw5-hhjh, GHSA-8gc5-j5rx-235r | +| @xmldom/xmldom | 0.9.8 | high | transitive | 0.9.10 | GHSA-2v35-w6hq-6mfw, GHSA-f6ww-3ggp-fr8h | +| axios | 1.13.5 | high | transitive | 1.15.2 | GHSA-3p68-rc4w-qgx5, GHSA-3w6x-2g7m-8v23 | +| devalue | 5.6.4 | high | transitive | 5.8.1 | GHSA-77vg-94rm-hx3p | +| fast-uri | 3.1.0 | high | transitive | 3.1.2 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | +| fast-xml-builder | 1.1.5 | high | transitive | 1.1.7 | GHSA-45c6-75p6-83cc, GHSA-5wm8-gmm8-39j9 | +| flatted | 3.3.3 | high | transitive | 3.4.2 | GHSA-25h7-pfq9-p65f, GHSA-rf6f-7fwh-wjgh | +| minimatch | 9.0.5 | high | transitive | 9.0.7 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 3.1.2 | high | transitive | 3.1.4 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 5.1.6 | high | transitive | 5.1.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| node-forge | 1.3.3 | high | transitive | 1.4.0 | GHSA-2328-f5f3-gj25, GHSA-5m6q-g25r-mvwx | +| path-to-regexp | 6.1.0 | high | transitive | 6.3.0 | GHSA-9wv6-86v2-598j | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | +| underscore | 1.13.7 | high | transitive | 1.13.8 | GHSA-qpx9-hpmf-5gmw | +| @fastify/static | 9.0.0 | medium | transitive | 9.1.1 | GHSA-pr96-94w5-mx2h, GHSA-x428-ghpx-8j92 | +| ajv | 6.12.6 | medium | transitive | 6.14.0 | GHSA-2g4f-4pwh-qvx6 | +| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | +| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | GHSA-f886-m6hf-6m8v | +| brace-expansion | 5.0.2 | medium | transitive | 5.0.6 | GHSA-f886-m6hf-6m8v, GHSA-jxxr-4gwj-5jf2 | +| esbuild | 0.17.19 | medium | direct | 0.25.0 | GHSA-67mh-4wv8-2f99 | +| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | +| hono | 4.12.16 | medium | transitive | 4.12.18 | GHSA-hm8q-7f3q-5f36, GHSA-p77w-8qqv-26rm | +| ip-address | 10.1.0 | medium | transitive | 10.1.1 | GHSA-v2v4-37r5-5v8g | +| protobufjs | 7.5.6 | medium | transitive | 7.5.8 | GHSA-jggg-4jg4-v7c6 | +| qs | 6.14.2 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | +| svelte | 5.55.3 | medium | transitive | 5.55.7 | GHSA-9rmh-mm8f-r9h6, GHSA-f3cj-j4f6-wq85 | +| turbo | 2.8.15 | medium | direct | 2.9.14 | GHSA-3qcw-2rhx-2726, GHSA-hcf7-66rw-9f5r | +| uuid | 8.3.2 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | +| uuid | 11.1.0 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | +| uuid | 13.0.0 | medium | transitive | 13.0.1 | GHSA-w5hq-g745-h8pq | +| ws | 8.18.0 | medium | transitive | 8.20.1 | GHSA-58qx-3vcg-4xpx | +| ws | 8.19.0 | medium | transitive | 8.20.1 | GHSA-58qx-3vcg-4xpx | +| yaml | 2.7.1 | medium | transitive | 2.8.3 | GHSA-48c2-rrv3-qjmp | +| diff | 7.0.0 | low | transitive | 8.0.3 | GHSA-73rr-hh4g-fpgx | --- diff --git a/website/docs/case-studies/camofox-browser.md b/website/docs/case-studies/camofox-browser.md index 858ffe7d..8c5673e4 100644 --- a/website/docs/case-studies/camofox-browser.md +++ b/website/docs/case-studies/camofox-browser.md @@ -68,19 +68,19 @@ Running only `npm update qs` fixes the body-parser chain but **leaves `qs@6.14.2 Both tools were run against the same `package-lock.json` on the same machine on 2026-06-05 (Node.js 22+, npm 10.x). -| Metric | npm audit | CVE Lite CLI v1.19.1 | -|---|---:|---:| -| Packages parsed / audited | 435 | 435 | -| Total reported findings | 2 | 2 | -| Critical | 0 | 0 | -| High | 0 | 0 | -| Moderate / Medium | 2 | 2 | -| Low | 0 | 0 | -| Direct vs transitive breakdown | ✗ | ✓ (0 / 2) | -| Deduplicated package view | ✓ | ✓ | -| Within-range vs parent-upgrade distinction | ✗ | ✓ | -| Copy-and-run command groups | partial | ✓ (2 groups) | -| Skipped findings with reason | ✗ | ✓ (0 skipped) | +| Metric | npm audit | CVE Lite CLI v1.19.1 | +| ------------------------------------------ | --------: | -------------------: | +| Packages parsed / audited | 435 | 435 | +| Total reported findings | 2 | 2 | +| Critical | 0 | 0 | +| High | 0 | 0 | +| Moderate / Medium | 2 | 2 | +| Low | 0 | 0 | +| Direct vs transitive breakdown | ✗ | ✓ (0 / 2) | +| Deduplicated package view | ✓ | ✓ | +| Within-range vs parent-upgrade distinction | ✗ | ✓ | +| Copy-and-run command groups | partial | ✓ (2 groups) | +| Skipped findings with reason | ✗ | ✓ (0 skipped) | **Why the totals align:** Both tools see the same two `qs` versions (`6.15.1` and `6.14.2`) on `CVE-2026-8723`. CVE Lite’s value is not inflating or deflating the count — it is **separating remediation strategy**: @@ -93,11 +93,11 @@ Both tools were run against the same `package-lock.json` on the same machine on No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 2 | 0 | 0 | 2 | 0 | 0 | 2 | 2 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 2 | 0 | 0 | 2 | 0 | 0 | 2 | 2 | -Two command groups covering two findings is the **ideal outcome** for a lean agent-runtime graph — the case study documents *how* CVE Lite chooses between within-range refresh and parent upgrade on the same CVE. +Two command groups covering two findings is the **ideal outcome** for a lean agent-runtime graph — the case study documents _how_ CVE Lite chooses between within-range refresh and parent upgrade on the same CVE. --- @@ -147,20 +147,20 @@ The example lockfile reflects CamoFox Browser at revision `ce3a3b085aacba73eb8de Every number in this case study comes from a live scan of the committed fixture at `examples/camofox-browser/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-06-05 | -| CLI version | v1.19.1 | -| CVE Lite command | `node dist/index.js examples/camofox-browser --verbose --all --json` | -| npm audit command | `npm audit` / `npm audit --json` (Node.js 22+) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/camofox-browser/package-lock.json` from [jo-inc/camofox-browser@ce3a3b0](https://github.com/jo-inc/camofox-browser/commit/ce3a3b085aacba73eb8de6c51733c19fb13bfae4) | -| Packages parsed (CVE Lite) | 435 | -| Unique vulnerable packages (CVE Lite) | 2 | -| Vulnerability entries (npm audit metadata) | 2 | -| Fix command groups (CVE Lite) | 2 | -| First-pass covered findings (CVE Lite) | 2 | -| Skipped findings with reason (CVE Lite) | 0 | +| Field | Value | +| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-06-05 | +| CLI version | v1.19.1 | +| CVE Lite command | `node dist/index.js examples/camofox-browser --verbose --all --json` | +| npm audit command | `npm audit` / `npm audit --json` (Node.js 22+) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/camofox-browser/package-lock.json` from [jo-inc/camofox-browser@ce3a3b0](https://github.com/jo-inc/camofox-browser/commit/ce3a3b085aacba73eb8de6c51733c19fb13bfae4) | +| Packages parsed (CVE Lite) | 435 | +| Unique vulnerable packages (CVE Lite) | 2 | +| Vulnerability entries (npm audit metadata) | 2 | +| Fix command groups (CVE Lite) | 2 | +| First-pass covered findings (CVE Lite) | 2 | +| Skipped findings with reason (CVE Lite) | 0 | Reproduce CVE Lite locally from the repository root: @@ -196,10 +196,10 @@ Both findings share **`CVE-2026-8723`** (remotely triggerable DoS in `qs.stringi Full vulnerable package list from the verified scan on 2026-06-05 (revision `ce3a3b0`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| qs | 6.15.1 | medium | transitive | 6.15.2 (within-range refresh) | CVE-2026-8723 | -| qs | 6.14.2 | medium | transitive | 6.15.2 (via express@4.22.2) | CVE-2026-8723 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ------- | ------- | -------- | ------------ | ----------------------------- | ------------- | +| qs | 6.15.1 | medium | transitive | 6.15.2 (within-range refresh) | CVE-2026-8723 | +| qs | 6.14.2 | medium | transitive | 6.15.2 (via express@4.22.2) | CVE-2026-8723 | --- diff --git a/website/docs/case-studies/gatsby.md b/website/docs/case-studies/gatsby.md index 6fa18632..8cc6d8e1 100644 --- a/website/docs/case-studies/gatsby.md +++ b/website/docs/case-studies/gatsby.md @@ -53,19 +53,19 @@ CVE Lite validates **`glob@10.5.0`** and **`js-yaml@4.1.1`** as high-severity di Both tools were run against the same `yarn.lock` on the same machine on 2026-05-29. -| Metric | yarn audit (1.22.19) | CVE Lite CLI v1.18.1 | -|---|---:|---:| -| Packages audited / parsed | 1,629 | 3,568 | -| Total reported findings | 281 | 128 | -| Critical | 25 | 9 | -| High | 123 | 66 | -| Moderate / Medium | 122 | 42 | -| Low | 11 | 11 | -| Direct vs transitive breakdown | ✗ | ✓ (5 / 123) | -| Deduplicated package view | ✗ | ✓ | -| Validated fix targets | ✗ | ✓ | -| Copy-and-run command groups | ✗ | ✓ (2 groups) | -| Skipped findings with reason | ✗ | ✓ (123 entries) | +| Metric | yarn audit (1.22.19) | CVE Lite CLI v1.18.1 | +| ------------------------------ | -------------------: | -------------------: | +| Packages audited / parsed | 1,629 | 3,568 | +| Total reported findings | 281 | 128 | +| Critical | 25 | 9 | +| High | 123 | 66 | +| Moderate / Medium | 122 | 42 | +| Low | 11 | 11 | +| Direct vs transitive breakdown | ✗ | ✓ (5 / 123) | +| Deduplicated package view | ✗ | ✓ | +| Validated fix targets | ✗ | ✓ | +| Copy-and-run command groups | ✗ | ✓ (2 groups) | +| Skipped findings with reason | ✗ | ✓ (123 entries) | **Why package counts differ:** `yarn audit` reports **1,629** audited packages from its dependency tree walk. CVE Lite parses **3,568** resolved package entries from the lockfile graph — a broader resolved view of the same `yarn.lock` snapshot. @@ -90,9 +90,9 @@ yarn add @babel/runtime@7.26.10 No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 128 | 9 | 66 | 42 | 11 | 5 | 123 | 2 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 128 | 9 | 66 | 42 | 11 | 5 | 123 | 2 | --- @@ -132,20 +132,20 @@ The example lockfile reflects Gatsby at revision `1f38c85963fd6bcfa9ccee2f925e5e Every number in this case study comes from a live scan of the committed fixture at `examples/gatsby/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-05-29 | -| CLI version | v1.18.1 | -| CVE Lite command | `node dist/index.js examples/gatsby --verbose --all --json` | -| yarn audit command | `yarn audit` / `yarn audit --json` (yarn 1.22.19) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/gatsby/yarn.lock` from [gatsbyjs/gatsby@1f38c85](https://github.com/gatsbyjs/gatsby/commit/1f38c85963fd6bcfa9ccee2f925e5e02b00eafbb) | -| Packages parsed (CVE Lite) | 3,568 | -| Unique vulnerable packages (CVE Lite) | 128 | -| Vulnerability entries (yarn audit) | 281 | -| Fix command groups (CVE Lite) | 2 | -| First-pass covered findings (CVE Lite) | 3 | -| Skipped findings with reason (CVE Lite) | 123 | +| Field | Value | +| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-05-29 | +| CLI version | v1.18.1 | +| CVE Lite command | `node dist/index.js examples/gatsby --verbose --all --json` | +| yarn audit command | `yarn audit` / `yarn audit --json` (yarn 1.22.19) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/gatsby/yarn.lock` from [gatsbyjs/gatsby@1f38c85](https://github.com/gatsbyjs/gatsby/commit/1f38c85963fd6bcfa9ccee2f925e5e02b00eafbb) | +| Packages parsed (CVE Lite) | 3,568 | +| Unique vulnerable packages (CVE Lite) | 128 | +| Vulnerability entries (yarn audit) | 281 | +| Fix command groups (CVE Lite) | 2 | +| First-pass covered findings (CVE Lite) | 3 | +| Skipped findings with reason (CVE Lite) | 123 | Reproduce CVE Lite locally from the repository root: @@ -184,136 +184,136 @@ Only **3 findings** have first-pass copy-and-run commands. The other **125** req Full vulnerable package list from the verified scan on 2026-05-29 (revision `1f38c85`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| @babel/traverse | 7.20.13 | critical | transitive | 7.23.2 | CVE-2023-45133 | -| form-data | 2.1.4 | critical | transitive | 2.5.4 | CVE-2025-7783 | -| form-data | 2.3.3 | critical | transitive | 2.5.4 | CVE-2025-7783 | -| form-data | 3.0.0 | critical | transitive | 3.0.4 | CVE-2025-7783 | -| handlebars | 4.7.7 | critical | transitive | 4.7.9 | CVE-2026-33916, CVE-2026-33937… | -| json-schema | 0.2.3 | critical | transitive | 0.4.0 | CVE-2021-3918 | -| loader-utils | 0.2.17 | critical | transitive | 1.4.1 | CVE-2022-37601 | -| parse-url | 6.0.0 | critical | transitive | 8.1.0 | CVE-2022-0722, CVE-2022-2216… | -| xmldom | 0.1.27 | critical | transitive | 0.5.0 | CVE-2026-41673, CVE-2021-32796… | -| @babel/plugin-transform-modules-systemjs | 7.20.11 | high | transitive | 7.29.4 | CVE-2026-44728 | -| @hapi/hoek | 6.2.4 | high | transitive | 8.5.1 | CVE-2020-36604 | -| @xmldom/xmldom | 0.7.10 | high | transitive | 0.8.13 | CVE-2026-41673, CVE-2026-41674… | -| @xmldom/xmldom | 0.8.6 | high | transitive | 0.8.13 | CVE-2026-41673, CVE-2026-41674… | -| ansi-html | 0.0.7 | high | transitive | 0.0.8 | CVE-2021-23424 | -| ansi-regex | 4.1.0 | high | transitive | 4.1.1 | CVE-2021-3807 | -| axios | 1.13.5 | high | transitive | 1.15.2 | CVE-2025-62718, CVE-2026-42044… | -| body-parser | 1.20.1 | high | transitive | 1.20.3 | CVE-2024-45590 | -| body-parser | 1.20.2 | high | transitive | 1.20.3 | CVE-2024-45590 | -| braces | 2.3.2 | high | transitive | 3.0.3 | CVE-2024-4068 | -| cross-spawn | 5.1.0 | high | transitive | 6.0.6 | CVE-2024-21538 | -| cross-spawn | 7.0.3 | high | transitive | 7.0.5 | CVE-2024-21538 | -| fast-json-patch | 3.0.0-1 | high | transitive | 3.1.1 | CVE-2021-4279 | -| flatted | 3.1.0 | high | transitive | 3.4.2 | CVE-2026-32141, CVE-2026-33228 | -| glob | 10.4.5 | high | direct | 10.5.0 | CVE-2025-64756 | -| hoek | 6.1.3 | high | transitive | 8.5.1 | CVE-2020-36604 | -| html-minifier | 4.0.0 | high | transitive | ⚠ no fix | CVE-2022-37620 | -| http-cache-semantics | 3.8.1 | high | transitive | 4.1.1 | CVE-2022-25881 | -| immutable | 3.7.6 | high | transitive | 3.8.3 | CVE-2026-29063 | -| ip | 1.1.5 | high | transitive | 1.1.9 | CVE-2024-29415, CVE-2023-42282 | -| js-yaml | 3.7.0 | high | direct | 3.14.2 | CVE-2025-64718 | -| json5 | 0.5.1 | high | transitive | 1.0.2 | CVE-2022-46175 | -| json5 | 1.0.1 | high | transitive | 1.0.2 | CVE-2022-46175 | -| jsonwebtoken | 8.5.1 | high | transitive | 9.0.0 | CVE-2022-23539, CVE-2022-23541… | -| jws | 3.2.2 | high | transitive | 3.2.3 | CVE-2025-65945 | -| loader-utils | 3.2.0 | high | transitive | 3.2.1 | CVE-2022-37603, CVE-2022-37599 | -| lodash | 4.17.21 | high | transitive | 4.18.0 | CVE-2026-2950, CVE-2026-4800… | -| lodash-es | 4.17.23 | high | transitive | 4.18.0 | CVE-2026-2950, CVE-2026-4800 | -| lodash.set | 4.3.2 | high | transitive | 4.17.19 | CVE-2020-8203 | -| lodash.template | 4.5.0 | high | transitive | 4.18.0 | CVE-2021-23337, CVE-2026-4800 | -| minimatch | 3.1.2 | high | transitive | 3.1.4 | CVE-2026-27904, CVE-2026-26996… | -| multer | 2.0.1 | high | transitive | 2.1.1 | CVE-2026-3520, CVE-2025-7338… | -| node-fetch | 2.1.2 | high | transitive | 2.6.7 | CVE-2022-0235, CVE-2020-15168 | -| normalize-url | 4.5.0 | high | transitive | 4.5.1 | CVE-2021-33502 | -| nth-check | 1.0.1 | high | transitive | 2.0.1 | CVE-2021-3803 | -| parse-git-config | 2.0.3 | high | transitive | ⚠ no fix | CVE-2025-25975 | -| parse-path | 4.0.1 | high | transitive | 5.0.0 | CVE-2022-0624 | -| path-to-regexp | 0.1.10 | high | transitive | 0.1.13 | CVE-2026-4867, CVE-2024-52798 | -| path-to-regexp | 0.1.12 | high | transitive | 0.1.13 | CVE-2026-4867 | -| path-to-regexp | 0.1.7 | high | transitive | 0.1.13 | CVE-2026-4867, CVE-2024-45296… | -| path-to-regexp | 1.7.0 | high | transitive | 1.9.0 | CVE-2024-45296 | -| path-to-regexp | 6.2.0 | high | transitive | 6.3.0 | CVE-2024-45296 | -| picomatch | 2.3.1 | high | transitive | 2.3.2 | CVE-2026-33672, CVE-2026-33671 | -| qs | 6.5.2 | high | transitive | 6.14.1 | CVE-2025-15284, CVE-2022-24999 | -| semver | 6.3.0 | high | transitive | 6.3.1 | CVE-2022-25883 | -| semver | 7.5.0 | high | transitive | 7.5.2 | CVE-2022-25883 | -| semver | 7.5.1 | high | transitive | 7.5.2 | CVE-2022-25883 | -| serialize-javascript | 4.0.0 | high | transitive | 7.0.3 | — | -| serialize-javascript | 5.0.1 | high | transitive | 7.0.5 | CVE-2026-34043 | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | CVE-2026-34043 | -| socket.io-parser | 4.2.4 | high | transitive | 4.2.6 | CVE-2026-33151 | -| svgo | 2.8.0 | high | transitive | 2.8.1 | CVE-2026-29074 | -| tar | 4.4.19 | high | transitive | 7.5.11 | CVE-2026-24842, CVE-2026-26960… | -| terser | 4.8.0 | high | transitive | 4.8.1 | CVE-2022-25858 | -| tmp | 0.0.33 | high | transitive | 0.2.6 | CVE-2025-54798, CVE-2026-44705 | -| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | -| trim | 0.0.1 | high | transitive | 0.0.3 | CVE-2020-7753 | -| trim-newlines | 1.0.0 | high | transitive | 3.0.1 | CVE-2021-33623 | -| trim-newlines | 2.0.0 | high | transitive | 3.0.1 | CVE-2021-33623 | -| ua-parser-js | 0.7.31 | high | transitive | 0.7.33 | CVE-2022-25927 | -| validator | 13.9.0 | high | transitive | 13.15.22 | CVE-2025-56200, CVE-2025-12758 | -| websocket-extensions | 0.1.3 | high | transitive | 0.1.4 | CVE-2020-7662 | -| ws | 7.5.5 | high | transitive | 7.5.10 | CVE-2024-37890 | -| ws | 8.11.0 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | -| xlsx | 0.18.3 | high | transitive | ⚠ no fix | CVE-2023-30533, CVE-2024-22363 | -| y18n | 4.0.0 | high | transitive | 4.0.1 | CVE-2020-7774 | -| @babel/helpers | 7.20.7 | medium | transitive | 7.26.10 | CVE-2025-27789 | -| @babel/runtime | 7.23.7 | medium | direct | 7.26.10 | CVE-2025-27789 | -| @babel/runtime-corejs3 | 7.10.3 | medium | transitive | 7.26.10 | CVE-2025-27789 | -| @octokit/plugin-paginate-rest | 1.1.2 | medium | transitive | 9.2.2 | CVE-2025-25288 | -| @octokit/request | 5.4.9 | medium | transitive | 8.4.1 | CVE-2025-25290 | -| @octokit/request-error | 1.0.4 | medium | transitive | 5.1.1 | CVE-2025-25289 | -| @octokit/request-error | 2.0.2 | medium | transitive | 5.1.1 | CVE-2025-25289 | -| @parcel/reporter-dev-server | 2.8.3 | medium | transitive | 2.16.4 | CVE-2025-56648 | -| ajv | 8.12.0 | medium | transitive | 8.18.0 | CVE-2025-69873 | -| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | CVE-2026-33750 | -| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | CVE-2026-33750 | -| express | 4.18.2 | medium | transitive | 4.20.0 | CVE-2024-43796, CVE-2024-29041 | -| file-type | 16.5.4 | medium | transitive | 21.3.1 | CVE-2026-31808 | -| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | — | -| got | 6.7.1 | medium | transitive | 11.8.5 | CVE-2022-33987 | -| hosted-git-info | 2.8.4 | medium | transitive | 2.8.9 | CVE-2021-23362 | -| js-yaml | 3.14.1 | medium | direct | 3.14.2 | CVE-2025-64718 | -| js-yaml | 4.1.0 | medium | direct | 4.1.1 | CVE-2025-64718 | -| katex | 0.12.0 | medium | transitive | 0.16.21 | CVE-2024-28246, CVE-2024-28243… | -| micromatch | 3.1.10 | medium | transitive | 4.0.8 | CVE-2024-4067 | -| postcss | 5.2.18 | medium | transitive | 8.5.10 | CVE-2021-23382, CVE-2023-44270… | -| postcss | 6.0.1 | medium | transitive | 8.5.10 | CVE-2021-23382, CVE-2023-44270… | -| postcss | 6.0.23 | medium | transitive | 8.5.10 | CVE-2021-23382, CVE-2023-44270… | -| postcss | 7.0.36 | medium | transitive | 8.5.10 | CVE-2023-44270, CVE-2026-41305 | -| postcss | 8.4.24 | medium | transitive | 8.5.10 | CVE-2023-44270, CVE-2026-41305 | -| prismjs | 1.29.0 | medium | transitive | 1.30.0 | CVE-2024-53382 | -| qs | 6.11.0 | medium | transitive | 6.14.2 | CVE-2025-15284, CVE-2026-2391 | -| qs | 6.13.0 | medium | transitive | 6.15.2 | CVE-2025-15284, CVE-2026-8723… | -| qs | 6.14.1 | medium | transitive | 6.15.2 | CVE-2026-8723, CVE-2026-2391 | -| react-devtools-core | 4.20.2 | medium | transitive | 4.28.4 | CVE-2023-5654 | -| request | 2.88.2 | medium | transitive | 3.0.0 | CVE-2023-28155 | -| tough-cookie | 2.5.0 | medium | transitive | 4.1.3 | CVE-2023-26136 | -| tough-cookie | 3.0.1 | medium | transitive | 4.1.3 | CVE-2023-26136 | -| trim-off-newlines | 1.0.1 | medium | transitive | 1.0.3 | CVE-2021-23425 | -| uuid | 3.4.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 9.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| vue-template-compiler | 2.7.14 | medium | transitive | ⚠ no fix | CVE-2024-6783 | -| word-wrap | 1.2.3 | medium | transitive | 1.2.4 | CVE-2023-26115 | -| ws | 8.17.1 | medium | transitive | 8.20.1 | CVE-2026-45736 | -| yaml | 1.10.0 | medium | transitive | 1.10.3 | CVE-2026-33532 | -| yaml | 2.8.2 | medium | transitive | 2.8.3 | CVE-2026-33532 | -| @tootallnate/once | 2.0.0 | low | transitive | 2.0.1 | CVE-2026-3449 | -| cookie | 0.4.2 | low | transitive | 0.7.0 | CVE-2024-47764 | -| cookie | 0.5.0 | low | transitive | 0.7.0 | CVE-2024-47764 | -| cookie | 0.6.0 | low | transitive | 0.7.0 | CVE-2024-47764 | -| diff | 4.0.1 | low | transitive | 4.0.4 | CVE-2026-24001 | -| es5-ext | 0.10.53 | low | transitive | 0.10.63 | CVE-2024-27088 | -| min-document | 2.19.0 | low | transitive | 2.19.1 | CVE-2025-57352 | -| on-headers | 1.0.2 | low | transitive | 1.1.0 | CVE-2025-7339 | -| send | 0.18.0 | low | transitive | 0.19.0 | CVE-2024-43799 | -| serve-static | 1.15.0 | low | transitive | 1.16.0 | CVE-2024-43800 | -| webpack | 5.98.0 | low | transitive | 5.104.1 | CVE-2025-68157, CVE-2025-68458 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ---------------------------------------- | ------- | -------- | ------------ | -------- | ------------------------------- | +| @babel/traverse | 7.20.13 | critical | transitive | 7.23.2 | CVE-2023-45133 | +| form-data | 2.1.4 | critical | transitive | 2.5.4 | CVE-2025-7783 | +| form-data | 2.3.3 | critical | transitive | 2.5.4 | CVE-2025-7783 | +| form-data | 3.0.0 | critical | transitive | 3.0.4 | CVE-2025-7783 | +| handlebars | 4.7.7 | critical | transitive | 4.7.9 | CVE-2026-33916, CVE-2026-33937… | +| json-schema | 0.2.3 | critical | transitive | 0.4.0 | CVE-2021-3918 | +| loader-utils | 0.2.17 | critical | transitive | 1.4.1 | CVE-2022-37601 | +| parse-url | 6.0.0 | critical | transitive | 8.1.0 | CVE-2022-0722, CVE-2022-2216… | +| xmldom | 0.1.27 | critical | transitive | 0.5.0 | CVE-2026-41673, CVE-2021-32796… | +| @babel/plugin-transform-modules-systemjs | 7.20.11 | high | transitive | 7.29.4 | CVE-2026-44728 | +| @hapi/hoek | 6.2.4 | high | transitive | 8.5.1 | CVE-2020-36604 | +| @xmldom/xmldom | 0.7.10 | high | transitive | 0.8.13 | CVE-2026-41673, CVE-2026-41674… | +| @xmldom/xmldom | 0.8.6 | high | transitive | 0.8.13 | CVE-2026-41673, CVE-2026-41674… | +| ansi-html | 0.0.7 | high | transitive | 0.0.8 | CVE-2021-23424 | +| ansi-regex | 4.1.0 | high | transitive | 4.1.1 | CVE-2021-3807 | +| axios | 1.13.5 | high | transitive | 1.15.2 | CVE-2025-62718, CVE-2026-42044… | +| body-parser | 1.20.1 | high | transitive | 1.20.3 | CVE-2024-45590 | +| body-parser | 1.20.2 | high | transitive | 1.20.3 | CVE-2024-45590 | +| braces | 2.3.2 | high | transitive | 3.0.3 | CVE-2024-4068 | +| cross-spawn | 5.1.0 | high | transitive | 6.0.6 | CVE-2024-21538 | +| cross-spawn | 7.0.3 | high | transitive | 7.0.5 | CVE-2024-21538 | +| fast-json-patch | 3.0.0-1 | high | transitive | 3.1.1 | CVE-2021-4279 | +| flatted | 3.1.0 | high | transitive | 3.4.2 | CVE-2026-32141, CVE-2026-33228 | +| glob | 10.4.5 | high | direct | 10.5.0 | CVE-2025-64756 | +| hoek | 6.1.3 | high | transitive | 8.5.1 | CVE-2020-36604 | +| html-minifier | 4.0.0 | high | transitive | ⚠ no fix | CVE-2022-37620 | +| http-cache-semantics | 3.8.1 | high | transitive | 4.1.1 | CVE-2022-25881 | +| immutable | 3.7.6 | high | transitive | 3.8.3 | CVE-2026-29063 | +| ip | 1.1.5 | high | transitive | 1.1.9 | CVE-2024-29415, CVE-2023-42282 | +| js-yaml | 3.7.0 | high | direct | 3.14.2 | CVE-2025-64718 | +| json5 | 0.5.1 | high | transitive | 1.0.2 | CVE-2022-46175 | +| json5 | 1.0.1 | high | transitive | 1.0.2 | CVE-2022-46175 | +| jsonwebtoken | 8.5.1 | high | transitive | 9.0.0 | CVE-2022-23539, CVE-2022-23541… | +| jws | 3.2.2 | high | transitive | 3.2.3 | CVE-2025-65945 | +| loader-utils | 3.2.0 | high | transitive | 3.2.1 | CVE-2022-37603, CVE-2022-37599 | +| lodash | 4.17.21 | high | transitive | 4.18.0 | CVE-2026-2950, CVE-2026-4800… | +| lodash-es | 4.17.23 | high | transitive | 4.18.0 | CVE-2026-2950, CVE-2026-4800 | +| lodash.set | 4.3.2 | high | transitive | 4.17.19 | CVE-2020-8203 | +| lodash.template | 4.5.0 | high | transitive | 4.18.0 | CVE-2021-23337, CVE-2026-4800 | +| minimatch | 3.1.2 | high | transitive | 3.1.4 | CVE-2026-27904, CVE-2026-26996… | +| multer | 2.0.1 | high | transitive | 2.1.1 | CVE-2026-3520, CVE-2025-7338… | +| node-fetch | 2.1.2 | high | transitive | 2.6.7 | CVE-2022-0235, CVE-2020-15168 | +| normalize-url | 4.5.0 | high | transitive | 4.5.1 | CVE-2021-33502 | +| nth-check | 1.0.1 | high | transitive | 2.0.1 | CVE-2021-3803 | +| parse-git-config | 2.0.3 | high | transitive | ⚠ no fix | CVE-2025-25975 | +| parse-path | 4.0.1 | high | transitive | 5.0.0 | CVE-2022-0624 | +| path-to-regexp | 0.1.10 | high | transitive | 0.1.13 | CVE-2026-4867, CVE-2024-52798 | +| path-to-regexp | 0.1.12 | high | transitive | 0.1.13 | CVE-2026-4867 | +| path-to-regexp | 0.1.7 | high | transitive | 0.1.13 | CVE-2026-4867, CVE-2024-45296… | +| path-to-regexp | 1.7.0 | high | transitive | 1.9.0 | CVE-2024-45296 | +| path-to-regexp | 6.2.0 | high | transitive | 6.3.0 | CVE-2024-45296 | +| picomatch | 2.3.1 | high | transitive | 2.3.2 | CVE-2026-33672, CVE-2026-33671 | +| qs | 6.5.2 | high | transitive | 6.14.1 | CVE-2025-15284, CVE-2022-24999 | +| semver | 6.3.0 | high | transitive | 6.3.1 | CVE-2022-25883 | +| semver | 7.5.0 | high | transitive | 7.5.2 | CVE-2022-25883 | +| semver | 7.5.1 | high | transitive | 7.5.2 | CVE-2022-25883 | +| serialize-javascript | 4.0.0 | high | transitive | 7.0.3 | — | +| serialize-javascript | 5.0.1 | high | transitive | 7.0.5 | CVE-2026-34043 | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | CVE-2026-34043 | +| socket.io-parser | 4.2.4 | high | transitive | 4.2.6 | CVE-2026-33151 | +| svgo | 2.8.0 | high | transitive | 2.8.1 | CVE-2026-29074 | +| tar | 4.4.19 | high | transitive | 7.5.11 | CVE-2026-24842, CVE-2026-26960… | +| terser | 4.8.0 | high | transitive | 4.8.1 | CVE-2022-25858 | +| tmp | 0.0.33 | high | transitive | 0.2.6 | CVE-2025-54798, CVE-2026-44705 | +| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | +| trim | 0.0.1 | high | transitive | 0.0.3 | CVE-2020-7753 | +| trim-newlines | 1.0.0 | high | transitive | 3.0.1 | CVE-2021-33623 | +| trim-newlines | 2.0.0 | high | transitive | 3.0.1 | CVE-2021-33623 | +| ua-parser-js | 0.7.31 | high | transitive | 0.7.33 | CVE-2022-25927 | +| validator | 13.9.0 | high | transitive | 13.15.22 | CVE-2025-56200, CVE-2025-12758 | +| websocket-extensions | 0.1.3 | high | transitive | 0.1.4 | CVE-2020-7662 | +| ws | 7.5.5 | high | transitive | 7.5.10 | CVE-2024-37890 | +| ws | 8.11.0 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | +| xlsx | 0.18.3 | high | transitive | ⚠ no fix | CVE-2023-30533, CVE-2024-22363 | +| y18n | 4.0.0 | high | transitive | 4.0.1 | CVE-2020-7774 | +| @babel/helpers | 7.20.7 | medium | transitive | 7.26.10 | CVE-2025-27789 | +| @babel/runtime | 7.23.7 | medium | direct | 7.26.10 | CVE-2025-27789 | +| @babel/runtime-corejs3 | 7.10.3 | medium | transitive | 7.26.10 | CVE-2025-27789 | +| @octokit/plugin-paginate-rest | 1.1.2 | medium | transitive | 9.2.2 | CVE-2025-25288 | +| @octokit/request | 5.4.9 | medium | transitive | 8.4.1 | CVE-2025-25290 | +| @octokit/request-error | 1.0.4 | medium | transitive | 5.1.1 | CVE-2025-25289 | +| @octokit/request-error | 2.0.2 | medium | transitive | 5.1.1 | CVE-2025-25289 | +| @parcel/reporter-dev-server | 2.8.3 | medium | transitive | 2.16.4 | CVE-2025-56648 | +| ajv | 8.12.0 | medium | transitive | 8.18.0 | CVE-2025-69873 | +| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | CVE-2026-33750 | +| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | CVE-2026-33750 | +| express | 4.18.2 | medium | transitive | 4.20.0 | CVE-2024-43796, CVE-2024-29041 | +| file-type | 16.5.4 | medium | transitive | 21.3.1 | CVE-2026-31808 | +| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | — | +| got | 6.7.1 | medium | transitive | 11.8.5 | CVE-2022-33987 | +| hosted-git-info | 2.8.4 | medium | transitive | 2.8.9 | CVE-2021-23362 | +| js-yaml | 3.14.1 | medium | direct | 3.14.2 | CVE-2025-64718 | +| js-yaml | 4.1.0 | medium | direct | 4.1.1 | CVE-2025-64718 | +| katex | 0.12.0 | medium | transitive | 0.16.21 | CVE-2024-28246, CVE-2024-28243… | +| micromatch | 3.1.10 | medium | transitive | 4.0.8 | CVE-2024-4067 | +| postcss | 5.2.18 | medium | transitive | 8.5.10 | CVE-2021-23382, CVE-2023-44270… | +| postcss | 6.0.1 | medium | transitive | 8.5.10 | CVE-2021-23382, CVE-2023-44270… | +| postcss | 6.0.23 | medium | transitive | 8.5.10 | CVE-2021-23382, CVE-2023-44270… | +| postcss | 7.0.36 | medium | transitive | 8.5.10 | CVE-2023-44270, CVE-2026-41305 | +| postcss | 8.4.24 | medium | transitive | 8.5.10 | CVE-2023-44270, CVE-2026-41305 | +| prismjs | 1.29.0 | medium | transitive | 1.30.0 | CVE-2024-53382 | +| qs | 6.11.0 | medium | transitive | 6.14.2 | CVE-2025-15284, CVE-2026-2391 | +| qs | 6.13.0 | medium | transitive | 6.15.2 | CVE-2025-15284, CVE-2026-8723… | +| qs | 6.14.1 | medium | transitive | 6.15.2 | CVE-2026-8723, CVE-2026-2391 | +| react-devtools-core | 4.20.2 | medium | transitive | 4.28.4 | CVE-2023-5654 | +| request | 2.88.2 | medium | transitive | 3.0.0 | CVE-2023-28155 | +| tough-cookie | 2.5.0 | medium | transitive | 4.1.3 | CVE-2023-26136 | +| tough-cookie | 3.0.1 | medium | transitive | 4.1.3 | CVE-2023-26136 | +| trim-off-newlines | 1.0.1 | medium | transitive | 1.0.3 | CVE-2021-23425 | +| uuid | 3.4.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 9.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| vue-template-compiler | 2.7.14 | medium | transitive | ⚠ no fix | CVE-2024-6783 | +| word-wrap | 1.2.3 | medium | transitive | 1.2.4 | CVE-2023-26115 | +| ws | 8.17.1 | medium | transitive | 8.20.1 | CVE-2026-45736 | +| yaml | 1.10.0 | medium | transitive | 1.10.3 | CVE-2026-33532 | +| yaml | 2.8.2 | medium | transitive | 2.8.3 | CVE-2026-33532 | +| @tootallnate/once | 2.0.0 | low | transitive | 2.0.1 | CVE-2026-3449 | +| cookie | 0.4.2 | low | transitive | 0.7.0 | CVE-2024-47764 | +| cookie | 0.5.0 | low | transitive | 0.7.0 | CVE-2024-47764 | +| cookie | 0.6.0 | low | transitive | 0.7.0 | CVE-2024-47764 | +| diff | 4.0.1 | low | transitive | 4.0.4 | CVE-2026-24001 | +| es5-ext | 0.10.53 | low | transitive | 0.10.63 | CVE-2024-27088 | +| min-document | 2.19.0 | low | transitive | 2.19.1 | CVE-2025-57352 | +| on-headers | 1.0.2 | low | transitive | 1.1.0 | CVE-2025-7339 | +| send | 0.18.0 | low | transitive | 0.19.0 | CVE-2024-43799 | +| serve-static | 1.15.0 | low | transitive | 1.16.0 | CVE-2024-43800 | +| webpack | 5.98.0 | low | transitive | 5.104.1 | CVE-2025-68157, CVE-2025-68458 | --- diff --git a/website/docs/case-studies/ghost.md b/website/docs/case-studies/ghost.md index 15e8345b..6f96f7c4 100644 --- a/website/docs/case-studies/ghost.md +++ b/website/docs/case-studies/ghost.md @@ -25,7 +25,7 @@ Ghost is a professionally maintained publishing platform with a dedicated securi The two most critical findings tell the story of why transitive risk is the hardest class of vulnerability to manage: -**`sanitize-html@2.17.0` — critical XSS.** Ghost uses `sanitize-html` to clean HTML submitted by editors and members before rendering it to readers. A critical XSS vulnerability in the library meant to make user content *safe* is precisely the kind of structural risk that a flat advisory list obscures. CVE Lite flags it immediately as critical with no known fix — telling you before you spend time looking that there is currently no version to upgrade to. +**`sanitize-html@2.17.0` — critical XSS.** Ghost uses `sanitize-html` to clean HTML submitted by editors and members before rendering it to readers. A critical XSS vulnerability in the library meant to make user content _safe_ is precisely the kind of structural risk that a flat advisory list obscures. CVE Lite flags it immediately as critical with no known fix — telling you before you spend time looking that there is currently no version to upgrade to. **`babel-traverse@6.26.0` — critical arbitrary code execution.** Six dependency layers deep in Ghost Admin's build toolchain: `@tryghost/ember-promise-modals` → `ember-auto-import` → `babel-core` → `babel-traverse`. An ancient Babel 6.x package from 2017, carrying a critical code execution CVE, present in every installation. A developer reading Ghost's `package.json` would never find it. A lockfile scanner does. @@ -45,19 +45,19 @@ A project can be doing everything right with automated update tooling and still Ghost uses pnpm. Both tools were run against the same `pnpm-lock.yaml` on the same machine. -| Metric | pnpm audit | CVE Lite CLI v1.16.0 | -|---|---:|---:| -| Total reported findings | 44 | 26 | -| Critical | 2 | 2 | -| High | 23 | 16 | -| Moderate / Medium | 18 | 7 | -| Low | 1 | 1 | -| Direct vs transitive breakdown | ✗ | ✓ (0 / 26) | -| Packages with no known fix flagged | ✗ | ✓ (3 packages) | -| Priority ordering (criticals first) | ✗ | ✓ | -| Parent chain identified | partial (paths shown) | ✓ | -| Validated fix targets | ✗ | ✓ | -| Specific copy-and-run commands | ✗ | ✓ (0 in this case — all transitive) | +| Metric | pnpm audit | CVE Lite CLI v1.16.0 | +| ----------------------------------- | --------------------: | ----------------------------------: | +| Total reported findings | 44 | 26 | +| Critical | 2 | 2 | +| High | 23 | 16 | +| Moderate / Medium | 18 | 7 | +| Low | 1 | 1 | +| Direct vs transitive breakdown | ✗ | ✓ (0 / 26) | +| Packages with no known fix flagged | ✗ | ✓ (3 packages) | +| Priority ordering (criticals first) | ✗ | ✓ | +| Parent chain identified | partial (paths shown) | ✓ | +| Validated fix targets | ✗ | ✓ | +| Specific copy-and-run commands | ✗ | ✓ (0 in this case — all transitive) | **Why CVE Lite reports fewer findings — and why that is not a coverage gap:** @@ -89,9 +89,9 @@ Both flag the finding. CVE Lite's `⚠ no fix` indicator surfaces immediately in No remediation pass was performed for this study. CVE Lite correctly identified that all 26 findings are transitive with no confident first-pass fix commands — meaning the usual direct-fix workflow does not apply here. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline | 26 | 2 | 16 | 7 | 1 | 0 | 26 | 0 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| -------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline | 26 | 2 | 16 | 7 | 1 | 0 | 26 | 0 | This result is itself meaningful. When a scanner generates zero copy-and-run commands, it is telling you something important: the remediation work is not in your direct dependency surface. It runs through upstream package releases and parent-chain decisions that require different effort — tracking Ghost's own internal packages, watching for Babel 7 migration in the admin toolchain, and monitoring upstream projects for the three packages that have no fix available at all. @@ -101,7 +101,7 @@ A tool that ignores this distinction and suggests `pnpm audit fix` regardless wo ## Fix Journey -Ghost's vulnerability surface is entirely in the transitive layer. The scanner's job here is not to generate install commands — it is to surface the nature of the risk clearly enough that the developer knows *not* to reach for `pnpm audit fix`. +Ghost's vulnerability surface is entirely in the transitive layer. The scanner's job here is not to generate install commands — it is to surface the nature of the risk clearly enough that the developer knows _not_ to reach for `pnpm audit fix`. The two critical findings follow different remediation paths: @@ -154,34 +154,34 @@ All 26 baseline findings remain open at the time of this study. No remediation w Full vulnerable package list at scan time (revision `359e702`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| babel-traverse | 6.26.0 | critical | transitive | 7.23.2 | GHSA-67hx-6x53-jw92 | -| sanitize-html | 2.17.0 | critical | transitive | ⚠ no fix | GHSA-rpr9-rxv7-x643 | -| @babel/plugin-transform-modules-systemjs | 7.29.0 | high | transitive | 7.29.4 | GHSA-fv7c-fp4j-7gwp | -| @tryghost/members-csv | 2.0.7 | high | transitive | 5.82.0 | GHSA-xgwh-cgv9-783v | -| fast-uri | 3.1.0 | high | transitive | 3.1.1 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | -| html-minifier | 4.0.0 | high | transitive | ⚠ no fix | GHSA-pfq8-rq6v-vf5m | -| jsonwebtoken | 8.5.1 | high | transitive | 9.0.0 | GHSA-8cf7-32gw-wr33, GHSA-hjrf-2m68-5957 | -| knex | 0.20.15 | high | transitive | 2.4.0 | GHSA-4jv9-3563-23j3 | -| knex | 0.21.21 | high | transitive | 2.4.0 | GHSA-4jv9-3563-23j3 | -| lodash.pick | 4.4.0 | high | transitive | 4.17.19 | GHSA-p6mc-m468-83gw | -| lodash.template | 4.5.0 | high | transitive | 4.17.21 | GHSA-35jh-r3h4-6jhm, GHSA-r5fr-rjxr-66jc | -| protobufjs | 7.5.5 | high | transitive | 1.1.1 | GHSA-2pr8-phx7-x9h3, GHSA-66ff-xgx4-vch8 | -| rollup | 0.57.1 | high | transitive | 2.79.2 | GHSA-gcx4-mw62-g8wm, GHSA-mw96-cpmx-2vgc | -| rollup | 1.32.1 | high | transitive | 2.79.2 | GHSA-gcx4-mw62-g8wm, GHSA-mw96-cpmx-2vgc | -| serialize-javascript | 4.0.0 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | -| validator | 13.12.0 | high | transitive | 13.15.20 | GHSA-9965-vmph-33xx, GHSA-vghf-hv5q-vc2g | -| validator | 7.2.0 | high | transitive | 13.7.0 | GHSA-9965-vmph-33xx, GHSA-qgmg-gppg-76gx | -| @protobufjs/utf8 | 1.1.0 | medium | transitive | 1.1.1 | GHSA-q6x5-8v7m-xcrf | -| express-brute | 1.0.1 | medium | transitive | ⚠ no fix | GHSA-984p-xq9m-4rjw | -| file-type | 16.5.4 | medium | transitive | 21.3.1 | GHSA-5v7r-6r5c-r473 | -| markdown-it | 8.4.2 | medium | transitive | 12.3.2 | GHSA-6vfc-qv3f-vr6c | -| postcss | 7.0.39 | medium | transitive | 8.4.31 | GHSA-7fh5-64p2-3v2j, GHSA-qx2v-qp2m-jg93 | -| postcss | 8.5.6 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | -| request | 2.88.2 | medium | transitive | 3.0.0 | GHSA-p8p7-x288-28g6 | -| elliptic | 6.6.1 | low | transitive | ⚠ no fix | GHSA-848j-6mx2-7j84 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ---------------------------------------- | ------- | -------- | ------------ | -------- | ---------------------------------------- | +| babel-traverse | 6.26.0 | critical | transitive | 7.23.2 | GHSA-67hx-6x53-jw92 | +| sanitize-html | 2.17.0 | critical | transitive | ⚠ no fix | GHSA-rpr9-rxv7-x643 | +| @babel/plugin-transform-modules-systemjs | 7.29.0 | high | transitive | 7.29.4 | GHSA-fv7c-fp4j-7gwp | +| @tryghost/members-csv | 2.0.7 | high | transitive | 5.82.0 | GHSA-xgwh-cgv9-783v | +| fast-uri | 3.1.0 | high | transitive | 3.1.1 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | +| html-minifier | 4.0.0 | high | transitive | ⚠ no fix | GHSA-pfq8-rq6v-vf5m | +| jsonwebtoken | 8.5.1 | high | transitive | 9.0.0 | GHSA-8cf7-32gw-wr33, GHSA-hjrf-2m68-5957 | +| knex | 0.20.15 | high | transitive | 2.4.0 | GHSA-4jv9-3563-23j3 | +| knex | 0.21.21 | high | transitive | 2.4.0 | GHSA-4jv9-3563-23j3 | +| lodash.pick | 4.4.0 | high | transitive | 4.17.19 | GHSA-p6mc-m468-83gw | +| lodash.template | 4.5.0 | high | transitive | 4.17.21 | GHSA-35jh-r3h4-6jhm, GHSA-r5fr-rjxr-66jc | +| protobufjs | 7.5.5 | high | transitive | 1.1.1 | GHSA-2pr8-phx7-x9h3, GHSA-66ff-xgx4-vch8 | +| rollup | 0.57.1 | high | transitive | 2.79.2 | GHSA-gcx4-mw62-g8wm, GHSA-mw96-cpmx-2vgc | +| rollup | 1.32.1 | high | transitive | 2.79.2 | GHSA-gcx4-mw62-g8wm, GHSA-mw96-cpmx-2vgc | +| serialize-javascript | 4.0.0 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | +| validator | 13.12.0 | high | transitive | 13.15.20 | GHSA-9965-vmph-33xx, GHSA-vghf-hv5q-vc2g | +| validator | 7.2.0 | high | transitive | 13.7.0 | GHSA-9965-vmph-33xx, GHSA-qgmg-gppg-76gx | +| @protobufjs/utf8 | 1.1.0 | medium | transitive | 1.1.1 | GHSA-q6x5-8v7m-xcrf | +| express-brute | 1.0.1 | medium | transitive | ⚠ no fix | GHSA-984p-xq9m-4rjw | +| file-type | 16.5.4 | medium | transitive | 21.3.1 | GHSA-5v7r-6r5c-r473 | +| markdown-it | 8.4.2 | medium | transitive | 12.3.2 | GHSA-6vfc-qv3f-vr6c | +| postcss | 7.0.39 | medium | transitive | 8.4.31 | GHSA-7fh5-64p2-3v2j, GHSA-qx2v-qp2m-jg93 | +| postcss | 8.5.6 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | +| request | 2.88.2 | medium | transitive | 3.0.0 | GHSA-p8p7-x288-28g6 | +| elliptic | 6.6.1 | low | transitive | ⚠ no fix | GHSA-848j-6mx2-7j84 | --- diff --git a/website/docs/case-studies/index.md b/website/docs/case-studies/index.md index 35625377..1b9cd6e0 100644 --- a/website/docs/case-studies/index.md +++ b/website/docs/case-studies/index.md @@ -13,26 +13,26 @@ CVE Lite CLI is an [OWASP Lab Project](https://owasp.org/cve-lite-cli/). --- -| Project | Lockfile | Key finding | -|---|---|---| -| [CamoFox Browser](./camofox-browser.md) | npm | AI agent browser automation — 435 packages, 2 `qs` findings, within-range refresh + express parent upgrade | -| [Ghost](./ghost.md) | npm | CMS platform, transitive chain analysis | -| [lint-staged](./lint-staged.md) | npm | `picomatch@2.3.1` direct high dep hidden by `npm audit --omit=dev` | -| [Lit](./lit.md) | npm | Web components reference implementation — 2,059 packages, 3 direct rollup findings with workspace-scoped fix commands, 5 critical transitive | -| [NestJS](./nestjs.md) | npm | 26 findings, 25 transitive - CVE Lite surfaces the one actionable direct fix | -| [OWASP Juice Shop](./owasp-juice-shop.md) | npm | Multiple critical/high direct findings with copy-and-run fix commands | -| [Payload CMS](./payload.md) | pnpm | TypeScript-first headless CMS — 2,602 packages, 1 direct finding, workspace-scoped remediation | -| [Presenton](./presenton.md) | npm (dual lockfile) | AI presentation generator — dual npm lockfiles (root + Electron), 9 findings, 5 fix groups | -| [Storybook](./storybook.md) | npm | Frontend tooling, large dependency graph | -| [Strapi](./strapi.md) | Yarn Berry | Headless CMS monorepo — 2,887 packages, 2 direct findings (`lodash`, `qs`), 15 transitive | -| [VS Code](./vscode.md) | npm | `@anthropic-ai/sdk@0.81/0.82` as direct Copilot dependencies | -| [Analog](./analog.md) | pnpm | Angular meta-framework monorepo, pnpm workspace scanning | -| [Astro](./astro.md) | pnpm | Large pnpm monorepo with verified baseline scan documentation | -| [LangChain.js](./langchainjs.md) | pnpm | LLM application framework monorepo — 2,174 packages, lean graph, 3 high with validated targets, malicious-package advisory on OpenSearch integration paths | -| [Mastra](./mastra.md) | pnpm | AI agent framework — 4,555 packages, 4 direct findings, workspace-scoped `pnpm add` | -| [n8n](./n8n.md) | pnpm | Workflow automation monorepo — 3,746 packages, 1 direct turbo fix, 4 command groups, 31 transitive | -| [OpenAI Agents SDK (JS)](./openai-agents-js.md) | pnpm | AI agent monorepo — 1,683 packages, 0 direct findings, 31 transitive, one verdaccio parent-upgrade command | -| [Turborepo](./turborepo.md) | pnpm | Monorepo build tooling, pnpm lockfile | -| [Vercel AI SDK](./vercel-ai-sdk.md) | pnpm | AI SDK monorepo — 3 direct findings, 5 workspace-scoped fix command groups | -| [Gatsby](./gatsby.md) | Yarn Classic | Large Yarn v1 monorepo — 3,568 packages, 128 findings, 5 direct | -| [Twenty](./twenty.md) | Yarn Berry | Open-source CRM — 5,451 packages, 105 findings, 0 direct, Nx orchestration layer | +| Project | Lockfile | Key finding | +| ----------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [CamoFox Browser](./camofox-browser.md) | npm | AI agent browser automation — 435 packages, 2 `qs` findings, within-range refresh + express parent upgrade | +| [Ghost](./ghost.md) | npm | CMS platform, transitive chain analysis | +| [lint-staged](./lint-staged.md) | npm | `picomatch@2.3.1` direct high dep hidden by `npm audit --omit=dev` | +| [Lit](./lit.md) | npm | Web components reference implementation — 2,059 packages, 3 direct rollup findings with workspace-scoped fix commands, 5 critical transitive | +| [NestJS](./nestjs.md) | npm | 26 findings, 25 transitive - CVE Lite surfaces the one actionable direct fix | +| [OWASP Juice Shop](./owasp-juice-shop.md) | npm | Multiple critical/high direct findings with copy-and-run fix commands | +| [Payload CMS](./payload.md) | pnpm | TypeScript-first headless CMS — 2,602 packages, 1 direct finding, workspace-scoped remediation | +| [Presenton](./presenton.md) | npm (dual lockfile) | AI presentation generator — dual npm lockfiles (root + Electron), 9 findings, 5 fix groups | +| [Storybook](./storybook.md) | npm | Frontend tooling, large dependency graph | +| [Strapi](./strapi.md) | Yarn Berry | Headless CMS monorepo — 2,887 packages, 2 direct findings (`lodash`, `qs`), 15 transitive | +| [VS Code](./vscode.md) | npm | `@anthropic-ai/sdk@0.81/0.82` as direct Copilot dependencies | +| [Analog](./analog.md) | pnpm | Angular meta-framework monorepo, pnpm workspace scanning | +| [Astro](./astro.md) | pnpm | Large pnpm monorepo with verified baseline scan documentation | +| [LangChain.js](./langchainjs.md) | pnpm | LLM application framework monorepo — 2,174 packages, lean graph, 3 high with validated targets, malicious-package advisory on OpenSearch integration paths | +| [Mastra](./mastra.md) | pnpm | AI agent framework — 4,555 packages, 4 direct findings, workspace-scoped `pnpm add` | +| [n8n](./n8n.md) | pnpm | Workflow automation monorepo — 3,746 packages, 1 direct turbo fix, 4 command groups, 31 transitive | +| [OpenAI Agents SDK (JS)](./openai-agents-js.md) | pnpm | AI agent monorepo — 1,683 packages, 0 direct findings, 31 transitive, one verdaccio parent-upgrade command | +| [Turborepo](./turborepo.md) | pnpm | Monorepo build tooling, pnpm lockfile | +| [Vercel AI SDK](./vercel-ai-sdk.md) | pnpm | AI SDK monorepo — 3 direct findings, 5 workspace-scoped fix command groups | +| [Gatsby](./gatsby.md) | Yarn Classic | Large Yarn v1 monorepo — 3,568 packages, 128 findings, 5 direct | +| [Twenty](./twenty.md) | Yarn Berry | Open-source CRM — 5,451 packages, 105 findings, 0 direct, Nx orchestration layer | diff --git a/website/docs/case-studies/langchainjs.md b/website/docs/case-studies/langchainjs.md index 18af0799..714cae32 100644 --- a/website/docs/case-studies/langchainjs.md +++ b/website/docs/case-studies/langchainjs.md @@ -52,20 +52,20 @@ Medium-severity noise is mostly **version fragmentation**: four distinct **`uuid Both tools were run against the same `pnpm-lock.yaml` on the same machine on 2026-05-30. -| Metric | pnpm audit (10.14.0) | CVE Lite CLI v1.18.1 | -|---|---:|---:| -| Total reported findings | 18 | 13 | -| Critical | 2 | 0 | -| High | 5 | 3 | -| Moderate / Medium | 9 | 8 | -| Low | 2 | 1 | -| Malicious / unknown bucket | (in critical) | 1 | -| Direct vs transitive breakdown | ✗ | ✓ (0 / 13) | -| Deduplicated package view | ✗ | ✓ | -| Malicious package explicitly flagged | partial | ✓ | -| Validated fix targets per package | partial | ✓ | -| Specific copy-and-run commands | ✗ | ✗ (0 groups) | -| Skipped findings with reason | ✗ | ✓ (13 entries) | +| Metric | pnpm audit (10.14.0) | CVE Lite CLI v1.18.1 | +| ------------------------------------ | -------------------: | -------------------: | +| Total reported findings | 18 | 13 | +| Critical | 2 | 0 | +| High | 5 | 3 | +| Moderate / Medium | 9 | 8 | +| Low | 2 | 1 | +| Malicious / unknown bucket | (in critical) | 1 | +| Direct vs transitive breakdown | ✗ | ✓ (0 / 13) | +| Deduplicated package view | ✗ | ✓ | +| Malicious package explicitly flagged | partial | ✓ | +| Validated fix targets per package | partial | ✓ | +| Specific copy-and-run commands | ✗ | ✗ (0 groups) | +| Skipped findings with reason | ✗ | ✓ (13 entries) | **Why the totals differ:** @@ -85,9 +85,9 @@ On this lockfile-only snapshot, CVE Lite generates **zero copy-and-run command g No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Malicious | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 13 | 0 | 3 | 8 | 1 | 1 | 0 | 13 | 0 | +| Stage | Findings | Critical | High | Medium | Low | Malicious | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | --------: | -----: | ---------: | -------------: | +| Baseline (verified) | 13 | 0 | 3 | 8 | 1 | 1 | 0 | 13 | 0 | Zero command groups on a **13-finding / 2,174-package** graph is a meaningful result: the first scan answer is “triage three high targets and one malicious integration — do not run blanket `pnpm audit fix` on a framework monorepo.” @@ -143,20 +143,20 @@ The example lockfile reflects LangChain.js at revision `1503c9beaa6a578f6a30739b Every number in this case study comes from a live scan of the committed fixture at `examples/langchainjs/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-05-30 | -| CLI version | v1.18.1 | -| CVE Lite command | `node dist/index.js examples/langchainjs --verbose --all --json` | -| pnpm audit command | `pnpm audit` / `pnpm audit --json` | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/langchainjs/pnpm-lock.yaml` from [langchain-ai/langchainjs@1503c9b](https://github.com/langchain-ai/langchainjs/commit/1503c9beaa6a578f6a30739b2cfc1af9d18dd805) | -| Packages parsed (CVE Lite) | 2,174 | -| Unique vulnerable packages (CVE Lite) | 13 | -| Vulnerability entries (pnpm audit) | 18 | -| Fix command groups (CVE Lite) | 0 | -| First-pass covered findings (CVE Lite) | 0 | -| Skipped findings with reason (CVE Lite) | 13 | +| Field | Value | +| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-05-30 | +| CLI version | v1.18.1 | +| CVE Lite command | `node dist/index.js examples/langchainjs --verbose --all --json` | +| pnpm audit command | `pnpm audit` / `pnpm audit --json` | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/langchainjs/pnpm-lock.yaml` from [langchain-ai/langchainjs@1503c9b](https://github.com/langchain-ai/langchainjs/commit/1503c9beaa6a578f6a30739b2cfc1af9d18dd805) | +| Packages parsed (CVE Lite) | 2,174 | +| Unique vulnerable packages (CVE Lite) | 13 | +| Vulnerability entries (pnpm audit) | 18 | +| Fix command groups (CVE Lite) | 0 | +| First-pass covered findings (CVE Lite) | 0 | +| Skipped findings with reason (CVE Lite) | 13 | Reproduce CVE Lite locally from the repository root: @@ -195,21 +195,21 @@ All 13 baseline findings remain open at the time of this study. No remediation w Full vulnerable package list from the verified scan on 2026-05-30 (revision `1503c9b`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| axios | 1.15.2 | high | transitive | 1.16.0 | CVE-2026-44494, CVE-2026-44489, CVE-2026-44490… | -| thrift | 0.21.0 | high | transitive | 0.23.0 | CVE-2026-43870, CVE-2026-41636 | -| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | -| ip-address | 10.1.0 | medium | transitive | 10.1.1 | CVE-2026-42338 | -| qs | 6.15.1 | medium | transitive | 6.15.2 | CVE-2026-8723 | -| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 9.0.1 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 10.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 11.1.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| ws | 8.18.0 | medium | transitive | 8.20.1 | CVE-2026-45736 | -| ws | 8.20.0 | medium | transitive | 8.20.1 | CVE-2026-45736 | -| @ai-sdk/provider-utils | 3.0.23 | low | transitive | 4.0.0 | CVE-2026-8769 | -| @opensearch-project/opensearch | 2.13.0 | unknown | transitive | ⚠ Malicious | MAL-2026-3434 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ------------------------------ | ------- | -------- | ------------ | ----------- | ----------------------------------------------- | +| axios | 1.15.2 | high | transitive | 1.16.0 | CVE-2026-44494, CVE-2026-44489, CVE-2026-44490… | +| thrift | 0.21.0 | high | transitive | 0.23.0 | CVE-2026-43870, CVE-2026-41636 | +| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | +| ip-address | 10.1.0 | medium | transitive | 10.1.1 | CVE-2026-42338 | +| qs | 6.15.1 | medium | transitive | 6.15.2 | CVE-2026-8723 | +| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 9.0.1 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 10.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 11.1.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| ws | 8.18.0 | medium | transitive | 8.20.1 | CVE-2026-45736 | +| ws | 8.20.0 | medium | transitive | 8.20.1 | CVE-2026-45736 | +| @ai-sdk/provider-utils | 3.0.23 | low | transitive | 4.0.0 | CVE-2026-8769 | +| @opensearch-project/opensearch | 2.13.0 | unknown | transitive | ⚠ Malicious | MAL-2026-3434 | --- diff --git a/website/docs/case-studies/lint-staged.md b/website/docs/case-studies/lint-staged.md index 084cd5a7..7fc4e76a 100644 --- a/website/docs/case-studies/lint-staged.md +++ b/website/docs/case-studies/lint-staged.md @@ -4,14 +4,14 @@ ## Summary -* **Project:** [lint-staged](https://github.com/lint-staged/lint-staged) — a widely used JavaScript developer workflow tool for running scripts against staged Git files -* **Revision:** `d3251b192d7116f059e7cabeffa3bfd7788dedeb` -* **Lockfile:** `package-lock.json` (417 resolved packages) -* **Baseline findings:** 3 unique vulnerable packages (2 high · 1 medium) -* **Direct vs transitive:** 1 direct / 2 transitive -* **Actionable direct fix:** `picomatch@2.3.1` → `picomatch@2.3.2` -* **Validated fix command generated:** 1 -* **Maintainer response:** acknowledged the report and confirmed the dependency would be updated +- **Project:** [lint-staged](https://github.com/lint-staged/lint-staged) — a widely used JavaScript developer workflow tool for running scripts against staged Git files +- **Revision:** `d3251b192d7116f059e7cabeffa3bfd7788dedeb` +- **Lockfile:** `package-lock.json` (417 resolved packages) +- **Baseline findings:** 3 unique vulnerable packages (2 high · 1 medium) +- **Direct vs transitive:** 1 direct / 2 transitive +- **Actionable direct fix:** `picomatch@2.3.1` → `picomatch@2.3.2` +- **Validated fix command generated:** 1 +- **Maintainer response:** acknowledged the report and confirmed the dependency would be updated --- @@ -44,9 +44,9 @@ The difference here is not coverage. It is visibility and structure. CVE Lite identifies: -* what is directly actionable -* what is transitive and requires deeper investigation -* what should not be turned into speculative upgrade commands +- what is directly actionable +- what is transitive and requires deeper investigation +- what should not be turned into speculative upgrade commands --- @@ -54,9 +54,9 @@ CVE Lite identifies: The baseline scan found 3 vulnerable packages: -* `picomatch@2.3.1` — high severity, direct, fixable -* `vite@8.0.2` — high severity, transitive / structural -* `brace-expansion@5.0.4` — medium severity, transitive / structural +- `picomatch@2.3.1` — high severity, direct, fixable +- `vite@8.0.2` — high severity, transitive / structural +- `brace-expansion@5.0.4` — medium severity, transitive / structural CVE Lite generated a single direct fix command: @@ -76,9 +76,9 @@ The most important outcome of this case study came from the maintainer response. This highlights a concrete gap in a common workflow: -* a high-severity vulnerability in a production dependency was present -* the standard audit command used by the maintainer did not surface it -* CVE Lite CLI identified it as a direct dependency with a clear fix +- a high-severity vulnerability in a production dependency was present +- the standard audit command used by the maintainer did not surface it +- CVE Lite CLI identified it as a direct dependency with a clear fix In this case, CVE Lite did not just report vulnerabilities — it surfaced an **actionable fix that was missed in the existing audit workflow**. @@ -92,16 +92,16 @@ Using `npm audit --omit=dev`, a high-severity vulnerability in a production depe CVE Lite CLI, scanning the resolved lockfile directly, was able to: -* identify the vulnerable dependency correctly -* classify it as a direct dependency -* provide a validated upgrade target (`2.3.2`) +- identify the vulnerable dependency correctly +- classify it as a direct dependency +- provide a validated upgrade target (`2.3.2`) The result is not just more visibility, but **a concrete remediation step that would otherwise have been missed**. For a pre-release check, that difference is critical: -* missing a vulnerability means no action is taken -* surfacing a validated fix enables immediate remediation +- missing a vulnerability means no action is taken +- surfacing a validated fix enables immediate remediation This case shows that the value is not only in detection, but in **ensuring actionable issues are not overlooked due to workflow assumptions**. @@ -115,9 +115,9 @@ https://github.com/lint-staged/lint-staged/issues/1763 The maintainer: -* acknowledged the issue -* confirmed the dependency would be updated -* identified that their existing audit workflow did not surface the vulnerability +- acknowledged the issue +- confirmed the dependency would be updated +- identified that their existing audit workflow did not surface the vulnerability --- @@ -131,10 +131,10 @@ A high-severity vulnerability in a production dependency was not surfaced due to CVE Lite’s value in this case is: -* scanning the resolved lockfile directly -* identifying the issue as a direct dependency -* providing a validated fix target -* separating it from unrelated transitive noise +- scanning the resolved lockfile directly +- identifying the issue as a direct dependency +- providing a validated fix target +- separating it from unrelated transitive noise For a pre-release check, that distinction is what enables action. @@ -152,8 +152,8 @@ npx cve-lite-cli . --verbose --all After applying the direct `picomatch` fix, the remaining issues are structural: -* `vite@8.0.2` — high severity, transitive -* `brace-expansion@5.0.4` — medium severity, transitive +- `vite@8.0.2` — high severity, transitive +- `brace-expansion@5.0.4` — medium severity, transitive These require dependency-chain investigation or upstream updates. diff --git a/website/docs/case-studies/lit.md b/website/docs/case-studies/lit.md index 91431283..94b11a03 100644 --- a/website/docs/case-studies/lit.md +++ b/website/docs/case-studies/lit.md @@ -51,17 +51,17 @@ CVE Lite validates separate upgrade targets for **`rollup@2.80.0`**, **`rollup@3 Both tools were run against the same `package-lock.json` on the same machine on 2026-05-30. -| Metric | npm audit | CVE Lite CLI v1.18.1 | -|---|---:|---:| -| Total reported findings | 107 | 99 | -| Critical | 7 | 5 | -| High | 65 | 52 | -| Moderate / Medium | 29 | 33 | -| Low | 6 | 9 | -| Direct vs transitive breakdown | ✗ | ✓ (3 / 96) | -| Deduplicated package view | ✗ | ✓ | -| npm workspace-scoped commands | ✗ | ✓ (4 groups) | -| Skipped findings with reason | ✗ | ✓ (77 entries) | +| Metric | npm audit | CVE Lite CLI v1.18.1 | +| ------------------------------ | --------: | -------------------: | +| Total reported findings | 107 | 99 | +| Critical | 7 | 5 | +| High | 65 | 52 | +| Moderate / Medium | 29 | 33 | +| Low | 6 | 9 | +| Direct vs transitive breakdown | ✗ | ✓ (3 / 96) | +| Deduplicated package view | ✗ | ✓ | +| npm workspace-scoped commands | ✗ | ✓ (4 groups) | +| Skipped findings with reason | ✗ | ✓ (77 entries) | **Why the totals differ:** `npm audit` counts **107 vulnerability entries** (advisory × path rows). CVE Lite counts **99 unique vulnerable package versions** once each. Nine `minimatch` majors each appear as separate unique packages in CVE Lite; `npm audit` may emit multiple rows per path. @@ -77,9 +77,9 @@ Both tools were run against the same `package-lock.json` on the same machine on No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 99 | 5 | 52 | 33 | 9 | 3 | 96 | 4 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 99 | 5 | 52 | 33 | 9 | 3 | 96 | 4 | --- @@ -119,20 +119,20 @@ The example lockfile reflects Lit at revision `20afabd3c5bfd49fdcdf1b8518e05c7f9 Every number in this case study comes from a live scan of the committed fixture at `examples/lit/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-05-30 | -| CLI version | v1.18.1 | -| CVE Lite command | `node dist/index.js examples/lit --verbose --all --json` | -| npm audit command | `npm audit` / `npm audit --json` | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/lit/package-lock.json` from [lit/lit@20afabd](https://github.com/lit/lit/commit/20afabd3c5bfd49fdcdf1b8518e05c7f99a46db6) | -| Packages parsed (CVE Lite) | 2,059 | -| Unique vulnerable packages (CVE Lite) | 99 | -| Vulnerability entries (npm audit) | 107 | -| Fix command groups (CVE Lite) | 4 | -| First-pass covered findings (CVE Lite) | 13 | -| Skipped findings with reason (CVE Lite) | 77 | +| Field | Value | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-05-30 | +| CLI version | v1.18.1 | +| CVE Lite command | `node dist/index.js examples/lit --verbose --all --json` | +| npm audit command | `npm audit` / `npm audit --json` | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/lit/package-lock.json` from [lit/lit@20afabd](https://github.com/lit/lit/commit/20afabd3c5bfd49fdcdf1b8518e05c7f99a46db6) | +| Packages parsed (CVE Lite) | 2,059 | +| Unique vulnerable packages (CVE Lite) | 99 | +| Vulnerability entries (npm audit) | 107 | +| Fix command groups (CVE Lite) | 4 | +| First-pass covered findings (CVE Lite) | 13 | +| Skipped findings with reason (CVE Lite) | 77 | Reproduce CVE Lite locally from the repository root: @@ -171,107 +171,107 @@ All 99 baseline findings remain open at the time of this study. No remediation w Full vulnerable package list from the verified scan on 2026-05-30 (revision `20afabd`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| basic-ftp | 5.0.5 | critical | transitive | 5.3.1 | CVE-2026-27699, CVE-2026-41324… | -| form-data | 2.3.3 | critical | transitive | 2.5.4 | CVE-2025-7783 | -| handlebars | 4.7.8 | critical | transitive | 4.7.9 | CVE-2026-33916, CVE-2026-33937… | -| liquidjs | 9.43.0 | critical | transitive | 10.26.0 | CVE-2026-44644, CVE-2022-25948… | -| minimist | 1.2.0 | critical | transitive | 1.2.6 | CVE-2020-7598, CVE-2021-44906 | -| @babel/plugin-transform-modules-systemjs | 7.27.1 | high | transitive | 7.29.4 | CVE-2026-44728 | -| @koa/cors | 3.4.3 | high | transitive | 5.0.0 | CVE-2023-49803 | -| @xmldom/xmldom | 0.8.11 | high | transitive | 0.8.13 | CVE-2026-41673, CVE-2026-41674… | -| axios | 0.21.4 | high | transitive | 0.32.0 | CVE-2026-44495, CVE-2025-62718… | -| axios | 0.26.1 | high | transitive | 0.32.0 | CVE-2026-44495, CVE-2025-62718… | -| axios | 1.11.0 | high | transitive | 1.16.0 | CVE-2026-44494, CVE-2026-44495… | -| braces | 2.3.2 | high | transitive | 3.0.3 | CVE-2024-4068 | -| compressing | 1.10.3 | high | transitive | 1.10.5 | CVE-2026-40931, CVE-2026-24884 | -| fast-uri | 3.0.6 | high | transitive | 3.1.2 | CVE-2026-6321, CVE-2026-6322 | -| flatted | 2.0.2 | high | transitive | 3.4.2 | CVE-2026-32141, CVE-2026-33228 | -| flatted | 3.3.3 | high | transitive | 3.4.2 | CVE-2026-32141, CVE-2026-33228 | -| glob | 10.4.5 | high | transitive | 10.5.0 | CVE-2025-64756 | -| immutable | 3.8.2 | high | transitive | 3.8.3 | CVE-2026-29063 | -| ip | 1.1.9 | high | transitive | ⚠ no fix | CVE-2024-29415 | -| js-cookie | 3.0.5 | high | transitive | 3.0.7 | CVE-2026-46625 | -| jws | 3.2.2 | high | transitive | 3.2.3 | CVE-2025-65945 | -| koa | 2.16.2 | high | transitive | 2.16.4 | CVE-2026-27959, CVE-2025-62595 | -| lodash | 4.17.21 | high | transitive | 4.18.0 | CVE-2026-2950, CVE-2026-4800… | -| minimatch | 3.0.4 | high | transitive | 3.1.4 | CVE-2026-27904, CVE-2026-26996… | -| minimatch | 3.1.2 | high | transitive | 3.1.4 | CVE-2026-27904, CVE-2026-26996… | -| minimatch | 5.1.6 | high | transitive | 5.1.8 | CVE-2026-27904, CVE-2026-26996… | -| minimatch | 6.2.0 | high | transitive | 6.2.2 | CVE-2026-27904, CVE-2026-26996… | -| minimatch | 7.4.6 | high | transitive | 7.4.8 | CVE-2026-27904, CVE-2026-26996… | -| minimatch | 9.0.1 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996… | -| minimatch | 9.0.3 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996… | -| minimatch | 9.0.5 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996… | -| next | 13.5.11 | high | transitive | 14.1.1 | CVE-2026-44573, CVE-2026-44572… | -| next | 14.2.32 | high | transitive | 15.5.16 | CVE-2026-44573, CVE-2026-44572… | -| next | 15.5.7 | high | transitive | 15.5.18 | CVE-2026-44575, CVE-2026-45109… | -| picomatch | 2.3.1 | high | transitive | 2.3.2 | CVE-2026-33672, CVE-2026-33671 | -| picomatch | 4.0.3 | high | transitive | 4.0.4 | CVE-2026-33672, CVE-2026-33671 | -| playwright | 1.55.0 | high | transitive | 1.55.1 | CVE-2025-59288 | -| preact | 10.27.1 | high | transitive | 10.27.3 | CVE-2026-22028 | -| rollup | 2.79.2 | high | direct | 2.80.0 | CVE-2026-27606 | -| rollup | 3.29.5 | high | direct | 3.30.0 | CVE-2026-27606 | -| rollup | 4.46.4 | high | direct | 4.59.0 | CVE-2026-27606 | -| serialize-javascript | 4.0.0 | high | transitive | 7.0.3 | — | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | CVE-2026-34043 | -| socket.io-parser | 3.3.4 | high | transitive | 3.3.5 | CVE-2026-33151 | -| socket.io-parser | 3.4.3 | high | transitive | 3.4.4 | CVE-2026-33151 | -| socket.io-parser | 4.2.4 | high | transitive | 4.2.6 | CVE-2026-33151 | -| systeminformation | 5.27.7 | high | transitive | 5.31.6 | CVE-2026-26318, CVE-2026-26280… | -| tar-fs | 2.1.1 | high | transitive | 2.1.4 | CVE-2025-48387, CVE-2024-12905… | -| tmp | 0.0.33 | high | transitive | 0.2.6 | CVE-2025-54798, CVE-2026-44705 | -| tmp | 0.2.1 | high | transitive | 0.2.6 | CVE-2025-54798, CVE-2026-44705 | -| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | -| trim | 0.0.1 | high | transitive | 0.0.3 | CVE-2020-7753 | -| ua-parser-js | 0.7.22 | high | transitive | 0.7.24 | CVE-2020-7793, CVE-2021-27292 | -| ws | 8.13.0 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | -| ws | 8.2.3 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | -| ws | 8.5.0 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | -| ws | 8.8.0 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | -| ajv | 6.12.6 | medium | transitive | 6.14.0 | CVE-2025-69873 | -| ajv | 8.17.1 | medium | transitive | 8.18.0 | CVE-2025-69873 | -| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | CVE-2026-33750 | -| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | CVE-2026-33750 | -| brace-expansion | 4.0.1 | medium | transitive | 5.0.5 | CVE-2026-33750 | -| esbuild | 0.17.19 | medium | transitive | 0.25.0 | — | -| esbuild | 0.18.20 | medium | transitive | 0.25.0 | — | -| esbuild | 0.21.5 | medium | transitive | 0.25.0 | — | -| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | — | -| ip-address | 10.0.1 | medium | transitive | 10.1.1 | CVE-2026-42338 | -| js-yaml | 3.13.1 | medium | transitive | 3.14.2 | CVE-2025-64718 | -| js-yaml | 3.14.1 | medium | transitive | 3.14.2 | CVE-2025-64718 | -| js-yaml | 4.1.0 | medium | transitive | 4.1.1 | CVE-2025-64718 | -| karma | 5.2.3 | medium | transitive | 6.3.16 | CVE-2022-0437, CVE-2021-23495 | -| log4js | 4.5.1 | medium | transitive | 6.4.0 | CVE-2022-21704 | -| micromatch | 3.1.10 | medium | transitive | 4.0.8 | CVE-2024-4067 | -| micromatch | 4.0.5 | medium | transitive | 4.0.8 | CVE-2024-4067 | -| parseuri | 0.0.6 | medium | transitive | ⚠ no fix | CVE-2024-36751 | -| postcss | 8.4.31 | medium | transitive | 8.5.10 | CVE-2026-41305 | -| postcss | 8.5.6 | medium | transitive | 8.5.10 | CVE-2026-41305 | -| qs | 6.13.0 | medium | transitive | 6.15.2 | CVE-2025-15284, CVE-2026-8723… | -| qs | 6.14.0 | medium | transitive | 6.15.2 | CVE-2025-15284, CVE-2026-8723… | -| qs | 6.5.3 | medium | transitive | 6.14.1 | CVE-2025-15284 | -| request | 2.88.2 | medium | transitive | 3.0.0 | CVE-2023-28155 | -| tough-cookie | 2.5.0 | medium | transitive | 4.1.3 | CVE-2023-26136 | -| useragent | 2.3.0 | medium | transitive | ⚠ no fix | CVE-2020-26311 | -| uuid | 3.4.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 9.0.1 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| vite | 4.5.14 | medium | transitive | 6.4.2 | CVE-2026-39365, CVE-2025-62522… | -| vite | 5.4.19 | medium | transitive | 6.4.2 | CVE-2026-39365, CVE-2025-62522… | -| ws | 8.17.1 | medium | transitive | 8.20.1 | CVE-2026-45736 | -| ws | 8.18.3 | medium | transitive | 8.20.1 | CVE-2026-45736 | -| yaml | 2.3.1 | medium | transitive | 2.8.3 | CVE-2026-33532 | -| cookie | 0.4.2 | low | transitive | 0.7.0 | CVE-2024-47764 | -| debug | 3.2.6 | low | transitive | 3.2.7 | CVE-2017-16137 | -| debug | 4.1.1 | low | transitive | 4.3.1 | CVE-2017-16137 | -| diff | 3.5.0 | low | transitive | 3.5.1 | CVE-2026-24001 | -| diff | 4.0.2 | low | transitive | 4.0.4 | CVE-2026-24001 | -| diff | 5.2.0 | low | transitive | 5.2.2 | CVE-2026-24001 | -| send | 0.16.2 | low | transitive | 0.19.0 | CVE-2024-43799 | -| serve-static | 1.13.2 | low | transitive | 1.16.0 | CVE-2024-43800 | -| webpack | 5.101.3 | low | transitive | 5.104.1 | CVE-2025-68157, CVE-2025-68458 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ---------------------------------------- | ------- | -------- | ------------ | -------- | ------------------------------- | +| basic-ftp | 5.0.5 | critical | transitive | 5.3.1 | CVE-2026-27699, CVE-2026-41324… | +| form-data | 2.3.3 | critical | transitive | 2.5.4 | CVE-2025-7783 | +| handlebars | 4.7.8 | critical | transitive | 4.7.9 | CVE-2026-33916, CVE-2026-33937… | +| liquidjs | 9.43.0 | critical | transitive | 10.26.0 | CVE-2026-44644, CVE-2022-25948… | +| minimist | 1.2.0 | critical | transitive | 1.2.6 | CVE-2020-7598, CVE-2021-44906 | +| @babel/plugin-transform-modules-systemjs | 7.27.1 | high | transitive | 7.29.4 | CVE-2026-44728 | +| @koa/cors | 3.4.3 | high | transitive | 5.0.0 | CVE-2023-49803 | +| @xmldom/xmldom | 0.8.11 | high | transitive | 0.8.13 | CVE-2026-41673, CVE-2026-41674… | +| axios | 0.21.4 | high | transitive | 0.32.0 | CVE-2026-44495, CVE-2025-62718… | +| axios | 0.26.1 | high | transitive | 0.32.0 | CVE-2026-44495, CVE-2025-62718… | +| axios | 1.11.0 | high | transitive | 1.16.0 | CVE-2026-44494, CVE-2026-44495… | +| braces | 2.3.2 | high | transitive | 3.0.3 | CVE-2024-4068 | +| compressing | 1.10.3 | high | transitive | 1.10.5 | CVE-2026-40931, CVE-2026-24884 | +| fast-uri | 3.0.6 | high | transitive | 3.1.2 | CVE-2026-6321, CVE-2026-6322 | +| flatted | 2.0.2 | high | transitive | 3.4.2 | CVE-2026-32141, CVE-2026-33228 | +| flatted | 3.3.3 | high | transitive | 3.4.2 | CVE-2026-32141, CVE-2026-33228 | +| glob | 10.4.5 | high | transitive | 10.5.0 | CVE-2025-64756 | +| immutable | 3.8.2 | high | transitive | 3.8.3 | CVE-2026-29063 | +| ip | 1.1.9 | high | transitive | ⚠ no fix | CVE-2024-29415 | +| js-cookie | 3.0.5 | high | transitive | 3.0.7 | CVE-2026-46625 | +| jws | 3.2.2 | high | transitive | 3.2.3 | CVE-2025-65945 | +| koa | 2.16.2 | high | transitive | 2.16.4 | CVE-2026-27959, CVE-2025-62595 | +| lodash | 4.17.21 | high | transitive | 4.18.0 | CVE-2026-2950, CVE-2026-4800… | +| minimatch | 3.0.4 | high | transitive | 3.1.4 | CVE-2026-27904, CVE-2026-26996… | +| minimatch | 3.1.2 | high | transitive | 3.1.4 | CVE-2026-27904, CVE-2026-26996… | +| minimatch | 5.1.6 | high | transitive | 5.1.8 | CVE-2026-27904, CVE-2026-26996… | +| minimatch | 6.2.0 | high | transitive | 6.2.2 | CVE-2026-27904, CVE-2026-26996… | +| minimatch | 7.4.6 | high | transitive | 7.4.8 | CVE-2026-27904, CVE-2026-26996… | +| minimatch | 9.0.1 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996… | +| minimatch | 9.0.3 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996… | +| minimatch | 9.0.5 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996… | +| next | 13.5.11 | high | transitive | 14.1.1 | CVE-2026-44573, CVE-2026-44572… | +| next | 14.2.32 | high | transitive | 15.5.16 | CVE-2026-44573, CVE-2026-44572… | +| next | 15.5.7 | high | transitive | 15.5.18 | CVE-2026-44575, CVE-2026-45109… | +| picomatch | 2.3.1 | high | transitive | 2.3.2 | CVE-2026-33672, CVE-2026-33671 | +| picomatch | 4.0.3 | high | transitive | 4.0.4 | CVE-2026-33672, CVE-2026-33671 | +| playwright | 1.55.0 | high | transitive | 1.55.1 | CVE-2025-59288 | +| preact | 10.27.1 | high | transitive | 10.27.3 | CVE-2026-22028 | +| rollup | 2.79.2 | high | direct | 2.80.0 | CVE-2026-27606 | +| rollup | 3.29.5 | high | direct | 3.30.0 | CVE-2026-27606 | +| rollup | 4.46.4 | high | direct | 4.59.0 | CVE-2026-27606 | +| serialize-javascript | 4.0.0 | high | transitive | 7.0.3 | — | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | CVE-2026-34043 | +| socket.io-parser | 3.3.4 | high | transitive | 3.3.5 | CVE-2026-33151 | +| socket.io-parser | 3.4.3 | high | transitive | 3.4.4 | CVE-2026-33151 | +| socket.io-parser | 4.2.4 | high | transitive | 4.2.6 | CVE-2026-33151 | +| systeminformation | 5.27.7 | high | transitive | 5.31.6 | CVE-2026-26318, CVE-2026-26280… | +| tar-fs | 2.1.1 | high | transitive | 2.1.4 | CVE-2025-48387, CVE-2024-12905… | +| tmp | 0.0.33 | high | transitive | 0.2.6 | CVE-2025-54798, CVE-2026-44705 | +| tmp | 0.2.1 | high | transitive | 0.2.6 | CVE-2025-54798, CVE-2026-44705 | +| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | +| trim | 0.0.1 | high | transitive | 0.0.3 | CVE-2020-7753 | +| ua-parser-js | 0.7.22 | high | transitive | 0.7.24 | CVE-2020-7793, CVE-2021-27292 | +| ws | 8.13.0 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | +| ws | 8.2.3 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | +| ws | 8.5.0 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | +| ws | 8.8.0 | high | transitive | 8.20.1 | CVE-2024-37890, CVE-2026-45736 | +| ajv | 6.12.6 | medium | transitive | 6.14.0 | CVE-2025-69873 | +| ajv | 8.17.1 | medium | transitive | 8.18.0 | CVE-2025-69873 | +| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | CVE-2026-33750 | +| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | CVE-2026-33750 | +| brace-expansion | 4.0.1 | medium | transitive | 5.0.5 | CVE-2026-33750 | +| esbuild | 0.17.19 | medium | transitive | 0.25.0 | — | +| esbuild | 0.18.20 | medium | transitive | 0.25.0 | — | +| esbuild | 0.21.5 | medium | transitive | 0.25.0 | — | +| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | — | +| ip-address | 10.0.1 | medium | transitive | 10.1.1 | CVE-2026-42338 | +| js-yaml | 3.13.1 | medium | transitive | 3.14.2 | CVE-2025-64718 | +| js-yaml | 3.14.1 | medium | transitive | 3.14.2 | CVE-2025-64718 | +| js-yaml | 4.1.0 | medium | transitive | 4.1.1 | CVE-2025-64718 | +| karma | 5.2.3 | medium | transitive | 6.3.16 | CVE-2022-0437, CVE-2021-23495 | +| log4js | 4.5.1 | medium | transitive | 6.4.0 | CVE-2022-21704 | +| micromatch | 3.1.10 | medium | transitive | 4.0.8 | CVE-2024-4067 | +| micromatch | 4.0.5 | medium | transitive | 4.0.8 | CVE-2024-4067 | +| parseuri | 0.0.6 | medium | transitive | ⚠ no fix | CVE-2024-36751 | +| postcss | 8.4.31 | medium | transitive | 8.5.10 | CVE-2026-41305 | +| postcss | 8.5.6 | medium | transitive | 8.5.10 | CVE-2026-41305 | +| qs | 6.13.0 | medium | transitive | 6.15.2 | CVE-2025-15284, CVE-2026-8723… | +| qs | 6.14.0 | medium | transitive | 6.15.2 | CVE-2025-15284, CVE-2026-8723… | +| qs | 6.5.3 | medium | transitive | 6.14.1 | CVE-2025-15284 | +| request | 2.88.2 | medium | transitive | 3.0.0 | CVE-2023-28155 | +| tough-cookie | 2.5.0 | medium | transitive | 4.1.3 | CVE-2023-26136 | +| useragent | 2.3.0 | medium | transitive | ⚠ no fix | CVE-2020-26311 | +| uuid | 3.4.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 9.0.1 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| vite | 4.5.14 | medium | transitive | 6.4.2 | CVE-2026-39365, CVE-2025-62522… | +| vite | 5.4.19 | medium | transitive | 6.4.2 | CVE-2026-39365, CVE-2025-62522… | +| ws | 8.17.1 | medium | transitive | 8.20.1 | CVE-2026-45736 | +| ws | 8.18.3 | medium | transitive | 8.20.1 | CVE-2026-45736 | +| yaml | 2.3.1 | medium | transitive | 2.8.3 | CVE-2026-33532 | +| cookie | 0.4.2 | low | transitive | 0.7.0 | CVE-2024-47764 | +| debug | 3.2.6 | low | transitive | 3.2.7 | CVE-2017-16137 | +| debug | 4.1.1 | low | transitive | 4.3.1 | CVE-2017-16137 | +| diff | 3.5.0 | low | transitive | 3.5.1 | CVE-2026-24001 | +| diff | 4.0.2 | low | transitive | 4.0.4 | CVE-2026-24001 | +| diff | 5.2.0 | low | transitive | 5.2.2 | CVE-2026-24001 | +| send | 0.16.2 | low | transitive | 0.19.0 | CVE-2024-43799 | +| serve-static | 1.13.2 | low | transitive | 1.16.0 | CVE-2024-43800 | +| webpack | 5.101.3 | low | transitive | 5.104.1 | CVE-2025-68157, CVE-2025-68458 | --- diff --git a/website/docs/case-studies/mastra.md b/website/docs/case-studies/mastra.md index c26975cf..e94ea2ca 100644 --- a/website/docs/case-studies/mastra.md +++ b/website/docs/case-studies/mastra.md @@ -48,19 +48,19 @@ CVE Lite validates **`path-to-regexp@8.4.0`** (covering both direct versions `0. Both tools were run against the same `pnpm-lock.yaml` on the same machine on 2026-05-29. -| Metric | pnpm audit (11.3.0) | CVE Lite CLI v1.18.1 | -|---|---:|---:| -| Packages audited / parsed | 4,555 | 4,555 | -| Total reported findings | 116 | 64 | -| Critical | 4 | 3 | -| High | 55 | 30 | -| Moderate / Medium | 51 | 25 | -| Low | 6 | 6 | -| Direct vs transitive breakdown | ✗ | ✓ (4 / 60) | -| Deduplicated package view | ✗ | ✓ | -| Validated fix targets | partial | ✓ | -| Copy-and-run command groups | ✗ | ✓ (2 groups) | -| Skipped findings with reason | ✗ | ✓ (60 entries) | +| Metric | pnpm audit (11.3.0) | CVE Lite CLI v1.18.1 | +| ------------------------------ | ------------------: | -------------------: | +| Packages audited / parsed | 4,555 | 4,555 | +| Total reported findings | 116 | 64 | +| Critical | 4 | 3 | +| High | 55 | 30 | +| Moderate / Medium | 51 | 25 | +| Low | 6 | 6 | +| Direct vs transitive breakdown | ✗ | ✓ (4 / 60) | +| Deduplicated package view | ✗ | ✓ | +| Validated fix targets | partial | ✓ | +| Copy-and-run command groups | ✗ | ✓ (2 groups) | +| Skipped findings with reason | ✗ | ✓ (60 entries) | **Why the totals differ:** `pnpm audit` counts **116 vulnerability entries** (advisory × path rows). CVE Lite counts **64 unique vulnerable package versions** once each. Example: four `minimatch` majors each appear as separate unique packages in CVE Lite; `pnpm audit` may emit multiple rows per advisory path. @@ -76,9 +76,9 @@ Both tools were run against the same `pnpm-lock.yaml` on the same machine on 202 No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 64 | 3 | 30 | 25 | 6 | 4 | 60 | 2 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 64 | 3 | 30 | 25 | 6 | 4 | 60 | 2 | --- @@ -118,20 +118,20 @@ The example lockfile reflects Mastra at revision `e9d54b281667477dd97b9dfc166b33 Every number in this case study comes from a live scan of the committed fixture at `examples/mastra/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-05-29 | -| CLI version | v1.18.1 | -| CVE Lite command | `node dist/index.js examples/mastra --verbose --all --json` | -| pnpm audit command | `pnpm audit` / `pnpm audit --json` (pnpm 11.3.0, Node.js 24) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/mastra/pnpm-lock.yaml` from [mastra-ai/mastra@e9d54b2](https://github.com/mastra-ai/mastra/commit/e9d54b281667477dd97b9dfc166b338f6d097fe8) | -| Packages parsed (CVE Lite) | 4,555 | -| Unique vulnerable packages (CVE Lite) | 64 | -| Vulnerability entries (pnpm audit) | 116 | -| Fix command groups (CVE Lite) | 2 | -| First-pass covered findings (CVE Lite) | 3 | -| Skipped findings with reason (CVE Lite) | 60 | +| Field | Value | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-05-29 | +| CLI version | v1.18.1 | +| CVE Lite command | `node dist/index.js examples/mastra --verbose --all --json` | +| pnpm audit command | `pnpm audit` / `pnpm audit --json` (pnpm 11.3.0, Node.js 24) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/mastra/pnpm-lock.yaml` from [mastra-ai/mastra@e9d54b2](https://github.com/mastra-ai/mastra/commit/e9d54b281667477dd97b9dfc166b338f6d097fe8) | +| Packages parsed (CVE Lite) | 4,555 | +| Unique vulnerable packages (CVE Lite) | 64 | +| Vulnerability entries (pnpm audit) | 116 | +| Fix command groups (CVE Lite) | 2 | +| First-pass covered findings (CVE Lite) | 3 | +| Skipped findings with reason (CVE Lite) | 60 | Reproduce CVE Lite locally from the repository root: @@ -170,72 +170,72 @@ Only **3 findings** have first-pass copy-and-run commands. The other **60** are Full vulnerable package list from the verified scan on 2026-05-29 (revision `e9d54b2`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| @clerk/shared | 3.28.2 | critical | transitive | 3.47.5 | CVE-2026-41248, CVE-2026-42349 | -| protobufjs | 7.5.4 | critical | transitive | 7.5.8 | CVE-2026-44294, CVE-2026-44293… | -| simple-git | 3.28.0 | critical | transitive | 3.36.0 | CVE-2026-6951, CVE-2026-28291… | -| @babel/plugin-transform-modules-systemjs | 7.28.5 | high | transitive | 7.29.4 | CVE-2026-44728 | -| @opentelemetry/auto-instrumentations-node | 0.56.1 | high | transitive | 0.75.0 | CVE-2026-44902 | -| @opentelemetry/exporter-prometheus | 0.207.0 | high | transitive | 0.217.0 | CVE-2026-44902 | -| @opentelemetry/exporter-prometheus | 0.208.0 | high | transitive | 0.217.0 | CVE-2026-44902 | -| @opentelemetry/exporter-prometheus | 0.57.2 | high | transitive | 0.217.0 | CVE-2026-44902 | -| @opentelemetry/sdk-node | 0.207.0 | high | transitive | 0.217.0 | CVE-2026-44902 | -| @opentelemetry/sdk-node | 0.208.0 | high | transitive | 0.217.0 | CVE-2026-44902 | -| @opentelemetry/sdk-node | 0.57.2 | high | transitive | 0.217.0 | CVE-2026-44902 | -| axios | 1.13.5 | high | transitive | 1.16.0 | CVE-2026-44494, CVE-2026-44495… | -| defu | 6.1.4 | high | transitive | 6.1.5 | CVE-2026-35209 | -| fast-uri | 3.1.0 | high | transitive | 3.1.2 | CVE-2026-6321, CVE-2026-6322 | -| js-cookie | 3.0.5 | high | transitive | 3.0.7 | CVE-2026-46625 | -| kysely | 0.28.8 | high | transitive | 0.28.17 | CVE-2026-33468, CVE-2026-44635… | -| langsmith | 0.5.26 | high | transitive | 0.6.0 | CVE-2026-45134 | -| lodash-es | 4.17.21 | high | transitive | 4.18.0 | CVE-2026-2950, CVE-2026-4800… | -| minimatch | 10.2.1 | high | transitive | 10.2.3 | CVE-2026-27904, CVE-2026-27903 | -| minimatch | 5.1.7 | high | transitive | 5.1.8 | CVE-2026-27904, CVE-2026-27903 | -| minimatch | 7.4.6 | high | transitive | 7.4.8 | CVE-2026-27904, CVE-2026-26996… | -| minimatch | 9.0.5 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996… | -| multer | 2.0.2 | high | transitive | 2.1.1 | CVE-2026-3520, CVE-2026-2359… | -| path-to-regexp | 0.1.12 | high | direct | 0.1.13 | CVE-2026-4867 | -| path-to-regexp | 8.3.0 | high | direct | 8.4.0 | CVE-2026-4923, CVE-2026-4926 | -| picomatch | 2.3.1 | high | transitive | 2.3.2 | CVE-2026-33672, CVE-2026-33671 | -| picomatch | 4.0.3 | high | transitive | 4.0.4 | CVE-2026-33672, CVE-2026-33671 | -| protobufjs | 7.5.5 | high | transitive | 7.5.8 | CVE-2026-44294, CVE-2026-44293… | -| protobufjs | 8.0.1 | high | transitive | 8.2.0 | CVE-2026-44294, CVE-2026-44293… | -| svgo | 3.3.2 | high | transitive | 3.3.3 | CVE-2026-29074 | -| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | -| undici | 6.22.0 | high | transitive | 6.24.0 | CVE-2026-1525, CVE-2026-1527… | -| vite | 7.3.1 | high | direct | 7.3.2 | CVE-2026-39365, CVE-2026-39363… | -| @fastify/static | 9.0.0 | medium | transitive | 9.1.1 | CVE-2026-6410, CVE-2026-6414 | -| @protobufjs/utf8 | 1.1.0 | medium | transitive | 1.1.1 | CVE-2026-44288 | -| @workos/authkit-session | 0.3.4 | medium | transitive | 0.5.1 | CVE-2026-42565 | -| ajv | 8.17.1 | medium | transitive | 8.18.0 | CVE-2025-69873 | -| better-auth | 1.4.18 | medium | transitive | 1.6.2 | — | -| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | CVE-2026-33750 | -| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | CVE-2026-33750 | -| brace-expansion | 5.0.4 | medium | transitive | 5.0.6 | CVE-2026-33750, CVE-2026-45149 | -| file-type | 20.4.1 | medium | transitive | 21.3.2 | CVE-2026-31808, CVE-2026-32630 | -| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | — | -| ip-address | 10.1.0 | medium | transitive | 10.1.1 | CVE-2026-42338 | -| mdast-util-to-hast | 13.2.0 | medium | transitive | 13.2.1 | CVE-2025-66400 | -| postcss | 8.5.8 | medium | transitive | 8.5.10 | CVE-2026-41305 | -| protobufjs | 7.5.7 | medium | transitive | 7.5.8 | CVE-2026-45740 | -| qs | 6.14.1 | medium | transitive | 6.15.2 | CVE-2026-8723, CVE-2026-2391 | -| turbo | 2.9.12 | medium | direct | 2.9.14 | CVE-2026-45772, CVE-2026-45773 | -| uuid | 10.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 11.1.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 13.0.0 | medium | transitive | 13.0.1 | CVE-2026-41907 | -| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 9.0.1 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| webpack-dev-server | 5.2.3 | medium | transitive | 5.2.4 | CVE-2026-6402 | -| ws | 8.18.0 | medium | transitive | 8.20.1 | CVE-2026-45736 | -| ws | 8.20.0 | medium | transitive | 8.20.1 | CVE-2026-45736 | -| yaml | 2.8.1 | medium | transitive | 2.8.3 | CVE-2026-33532 | -| @ai-sdk/provider-utils | 2.1.10 | low | transitive | 4.0.0 | CVE-2026-8769 | -| @ai-sdk/provider-utils | 2.2.8 | low | transitive | 4.0.0 | CVE-2026-8769 | -| @ai-sdk/provider-utils | 3.0.20 | low | transitive | 4.0.0 | CVE-2026-8769 | -| @ai-sdk/provider-utils | 3.0.25 | low | transitive | 4.0.0 | CVE-2026-8769 | -| @wong2/mcp-cli | 1.13.0 | low | transitive | 2.0.0 | CVE-2025-9262 | -| ai | 4.3.19 | low | transitive | 5.0.52 | CVE-2025-48985 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ----------------------------------------- | ------- | -------- | ------------ | -------- | ------------------------------- | +| @clerk/shared | 3.28.2 | critical | transitive | 3.47.5 | CVE-2026-41248, CVE-2026-42349 | +| protobufjs | 7.5.4 | critical | transitive | 7.5.8 | CVE-2026-44294, CVE-2026-44293… | +| simple-git | 3.28.0 | critical | transitive | 3.36.0 | CVE-2026-6951, CVE-2026-28291… | +| @babel/plugin-transform-modules-systemjs | 7.28.5 | high | transitive | 7.29.4 | CVE-2026-44728 | +| @opentelemetry/auto-instrumentations-node | 0.56.1 | high | transitive | 0.75.0 | CVE-2026-44902 | +| @opentelemetry/exporter-prometheus | 0.207.0 | high | transitive | 0.217.0 | CVE-2026-44902 | +| @opentelemetry/exporter-prometheus | 0.208.0 | high | transitive | 0.217.0 | CVE-2026-44902 | +| @opentelemetry/exporter-prometheus | 0.57.2 | high | transitive | 0.217.0 | CVE-2026-44902 | +| @opentelemetry/sdk-node | 0.207.0 | high | transitive | 0.217.0 | CVE-2026-44902 | +| @opentelemetry/sdk-node | 0.208.0 | high | transitive | 0.217.0 | CVE-2026-44902 | +| @opentelemetry/sdk-node | 0.57.2 | high | transitive | 0.217.0 | CVE-2026-44902 | +| axios | 1.13.5 | high | transitive | 1.16.0 | CVE-2026-44494, CVE-2026-44495… | +| defu | 6.1.4 | high | transitive | 6.1.5 | CVE-2026-35209 | +| fast-uri | 3.1.0 | high | transitive | 3.1.2 | CVE-2026-6321, CVE-2026-6322 | +| js-cookie | 3.0.5 | high | transitive | 3.0.7 | CVE-2026-46625 | +| kysely | 0.28.8 | high | transitive | 0.28.17 | CVE-2026-33468, CVE-2026-44635… | +| langsmith | 0.5.26 | high | transitive | 0.6.0 | CVE-2026-45134 | +| lodash-es | 4.17.21 | high | transitive | 4.18.0 | CVE-2026-2950, CVE-2026-4800… | +| minimatch | 10.2.1 | high | transitive | 10.2.3 | CVE-2026-27904, CVE-2026-27903 | +| minimatch | 5.1.7 | high | transitive | 5.1.8 | CVE-2026-27904, CVE-2026-27903 | +| minimatch | 7.4.6 | high | transitive | 7.4.8 | CVE-2026-27904, CVE-2026-26996… | +| minimatch | 9.0.5 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996… | +| multer | 2.0.2 | high | transitive | 2.1.1 | CVE-2026-3520, CVE-2026-2359… | +| path-to-regexp | 0.1.12 | high | direct | 0.1.13 | CVE-2026-4867 | +| path-to-regexp | 8.3.0 | high | direct | 8.4.0 | CVE-2026-4923, CVE-2026-4926 | +| picomatch | 2.3.1 | high | transitive | 2.3.2 | CVE-2026-33672, CVE-2026-33671 | +| picomatch | 4.0.3 | high | transitive | 4.0.4 | CVE-2026-33672, CVE-2026-33671 | +| protobufjs | 7.5.5 | high | transitive | 7.5.8 | CVE-2026-44294, CVE-2026-44293… | +| protobufjs | 8.0.1 | high | transitive | 8.2.0 | CVE-2026-44294, CVE-2026-44293… | +| svgo | 3.3.2 | high | transitive | 3.3.3 | CVE-2026-29074 | +| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | +| undici | 6.22.0 | high | transitive | 6.24.0 | CVE-2026-1525, CVE-2026-1527… | +| vite | 7.3.1 | high | direct | 7.3.2 | CVE-2026-39365, CVE-2026-39363… | +| @fastify/static | 9.0.0 | medium | transitive | 9.1.1 | CVE-2026-6410, CVE-2026-6414 | +| @protobufjs/utf8 | 1.1.0 | medium | transitive | 1.1.1 | CVE-2026-44288 | +| @workos/authkit-session | 0.3.4 | medium | transitive | 0.5.1 | CVE-2026-42565 | +| ajv | 8.17.1 | medium | transitive | 8.18.0 | CVE-2025-69873 | +| better-auth | 1.4.18 | medium | transitive | 1.6.2 | — | +| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | CVE-2026-33750 | +| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | CVE-2026-33750 | +| brace-expansion | 5.0.4 | medium | transitive | 5.0.6 | CVE-2026-33750, CVE-2026-45149 | +| file-type | 20.4.1 | medium | transitive | 21.3.2 | CVE-2026-31808, CVE-2026-32630 | +| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | — | +| ip-address | 10.1.0 | medium | transitive | 10.1.1 | CVE-2026-42338 | +| mdast-util-to-hast | 13.2.0 | medium | transitive | 13.2.1 | CVE-2025-66400 | +| postcss | 8.5.8 | medium | transitive | 8.5.10 | CVE-2026-41305 | +| protobufjs | 7.5.7 | medium | transitive | 7.5.8 | CVE-2026-45740 | +| qs | 6.14.1 | medium | transitive | 6.15.2 | CVE-2026-8723, CVE-2026-2391 | +| turbo | 2.9.12 | medium | direct | 2.9.14 | CVE-2026-45772, CVE-2026-45773 | +| uuid | 10.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 11.1.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 13.0.0 | medium | transitive | 13.0.1 | CVE-2026-41907 | +| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 9.0.1 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| webpack-dev-server | 5.2.3 | medium | transitive | 5.2.4 | CVE-2026-6402 | +| ws | 8.18.0 | medium | transitive | 8.20.1 | CVE-2026-45736 | +| ws | 8.20.0 | medium | transitive | 8.20.1 | CVE-2026-45736 | +| yaml | 2.8.1 | medium | transitive | 2.8.3 | CVE-2026-33532 | +| @ai-sdk/provider-utils | 2.1.10 | low | transitive | 4.0.0 | CVE-2026-8769 | +| @ai-sdk/provider-utils | 2.2.8 | low | transitive | 4.0.0 | CVE-2026-8769 | +| @ai-sdk/provider-utils | 3.0.20 | low | transitive | 4.0.0 | CVE-2026-8769 | +| @ai-sdk/provider-utils | 3.0.25 | low | transitive | 4.0.0 | CVE-2026-8769 | +| @wong2/mcp-cli | 1.13.0 | low | transitive | 2.0.0 | CVE-2025-9262 | +| ai | 4.3.19 | low | transitive | 5.0.52 | CVE-2025-48985 | --- diff --git a/website/docs/case-studies/n8n.md b/website/docs/case-studies/n8n.md index 99f75f6d..70dbc4e0 100644 --- a/website/docs/case-studies/n8n.md +++ b/website/docs/case-studies/n8n.md @@ -55,19 +55,19 @@ This is a **lockfile refresh** pattern (v1.19.1 within-range transitive fix): th Both tools were run against the same `pnpm-lock.yaml` on the same machine on 2026-06-02 (Node.js 22+, pnpm 10.14.0). -| Metric | pnpm audit (10.14.0) | CVE Lite CLI v1.19.1 | -|---|---:|---:| -| Packages audited / parsed | 3,746 | 3,746 | -| Total reported findings | 51 | 32 | -| Critical | 0 | 0 | -| High | 19 | 13 | -| Moderate / Medium | 25 | 14 | -| Low | 7 | 5 | -| Direct vs transitive breakdown | ✗ | ✓ (1 / 31) | -| Deduplicated package view | ✗ | ✓ | -| Parent package named per finding | partial (paths) | ✓ | -| Specific copy-and-run commands | partial | ✓ (4 groups) | -| Skipped findings with reason | ✗ | ✓ (28 entries) | +| Metric | pnpm audit (10.14.0) | CVE Lite CLI v1.19.1 | +| -------------------------------- | -------------------: | -------------------: | +| Packages audited / parsed | 3,746 | 3,746 | +| Total reported findings | 51 | 32 | +| Critical | 0 | 0 | +| High | 19 | 13 | +| Moderate / Medium | 25 | 14 | +| Low | 7 | 5 | +| Direct vs transitive breakdown | ✗ | ✓ (1 / 31) | +| Deduplicated package view | ✗ | ✓ | +| Parent package named per finding | partial (paths) | ✓ | +| Specific copy-and-run commands | partial | ✓ (4 groups) | +| Skipped findings with reason | ✗ | ✓ (28 entries) | **Why the totals differ:** `pnpm audit` counts **51 vulnerability entries** (advisory × dependency path rows in metadata). CVE Lite counts **32 unique vulnerable package versions** once each. Example: three `minimatch` majors are **three rows** in CVE Lite and **multiple path rows** in `pnpm audit`. @@ -83,9 +83,9 @@ Both tools were run against the same `pnpm-lock.yaml` on the same machine on 202 No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 32 | 0 | 13 | 14 | 5 | 1 | 31 | 4 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 32 | 0 | 13 | 14 | 5 | 1 | 31 | 4 | Four command groups covering four findings (with partial coverage on one low finding) is a **realistic first pass** on a 3,746-package integration monorepo — the case study documents where triage time goes after the direct `turbo` bump. @@ -139,20 +139,20 @@ The example lockfile reflects n8n at revision `e2e03948562e1c744be4ef7898b3b754f Every number in this case study comes from a live scan of the committed fixture at `examples/n8n/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-06-02 | -| CLI version | v1.19.1 | -| CVE Lite command | `node dist/index.js examples/n8n --verbose --all --json` | -| pnpm audit command | `pnpm audit` / `pnpm audit --json` (Node.js 22+, pnpm 10.14.0) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/n8n/pnpm-lock.yaml` from [n8n-io/n8n@e2e0394](https://github.com/n8n-io/n8n/commit/e2e03948562e1c744be4ef7898b3b754fbdb6cf9) | -| Packages parsed (CVE Lite) | 3,746 | -| Unique vulnerable packages (CVE Lite) | 32 | -| Vulnerability entries (pnpm audit metadata) | 51 | -| Fix command groups (CVE Lite) | 4 | -| First-pass covered findings (CVE Lite) | 4 | -| Skipped findings with reason (CVE Lite) | 28 | +| Field | Value | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-06-02 | +| CLI version | v1.19.1 | +| CVE Lite command | `node dist/index.js examples/n8n --verbose --all --json` | +| pnpm audit command | `pnpm audit` / `pnpm audit --json` (Node.js 22+, pnpm 10.14.0) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/n8n/pnpm-lock.yaml` from [n8n-io/n8n@e2e0394](https://github.com/n8n-io/n8n/commit/e2e03948562e1c744be4ef7898b3b754fbdb6cf9) | +| Packages parsed (CVE Lite) | 3,746 | +| Unique vulnerable packages (CVE Lite) | 32 | +| Vulnerability entries (pnpm audit metadata) | 51 | +| Fix command groups (CVE Lite) | 4 | +| First-pass covered findings (CVE Lite) | 4 | +| Skipped findings with reason (CVE Lite) | 28 | Reproduce CVE Lite locally from the repository root: @@ -190,40 +190,40 @@ All 32 baseline findings remain open at the time of this study. No remediation w Full vulnerable package list from the verified scan on 2026-06-02 (revision `e2e0394`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| @babel/plugin-transform-modules-systemjs | 7.29.0 | high | transitive | 7.29.4 | CVE-2026-44728 | -| html-minifier | 4.0.0 | high | transitive | — | CVE-2022-37620 | -| minimatch | 8.0.4 | high | transitive | 8.0.6 | CVE-2026-27904, CVE-2026-26996, CVE-2026-27903 | -| minimatch | 9.0.1 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996, CVE-2026-27903 | -| minimatch | 9.0.3 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996, CVE-2026-27903 | -| rollup | 2.79.2 | high | transitive | 2.80.0 | CVE-2026-27606 | -| rollup | 4.52.4 | high | transitive | 4.59.0 | CVE-2026-27606 | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | CVE-2026-34043 | -| storybook | 10.1.11 | high | transitive | 10.2.10 | CVE-2026-27148 | -| svgo | 3.3.2 | high | transitive | 3.3.3 | CVE-2026-29074 | -| tmp | 0.2.4 | high | transitive | 0.2.6 | CVE-2026-44705 | -| vite | 8.0.2 | high | transitive | 8.0.5 | CVE-2026-39365, CVE-2026-39363, CVE-2026-39364 | -| xlsx | https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz | high | unknown | — | CVE-2023-30533, CVE-2024-22363 | -| dompurify | 3.1.7 | medium | transitive | 3.4.0 | CVE-2026-41239, CVE-2026-41240, CVE-2026-0540… (+3) | -| element-plus | 2.4.3 | medium | transitive | 2.11.1 | CVE-2025-57665 | -| file-type | 16.5.4 | medium | transitive | 21.3.1 | CVE-2026-31808 | -| ip-address | 9.0.5 | medium | transitive | 10.1.1 | CVE-2026-42338 | -| markdown-it | 13.0.2 | medium | transitive | 14.1.1 | CVE-2026-2327 | -| mjml | 4.15.3 | medium | transitive | 5.0.0 | CVE-2025-67898 | -| nodemailer | 7.0.11 | medium | transitive | 8.0.5 | GHSA-c7w3-x93f-qmm8, GHSA-vvjj-xcjg-gr5g | -| postcss | 8.4.31 | medium | transitive | 8.5.10 | CVE-2026-41305 | -| postcss | 8.5.8 | medium | transitive | 8.5.10 | CVE-2026-41305 | -| prismjs | 1.29.0 | medium | transitive | 1.30.0 | CVE-2024-53382 | -| qs | 6.14.2 | medium | transitive | 6.15.2 | CVE-2026-8723 | -| showdown | 2.1.0 | medium | transitive | — | CVE-2024-1899 | -| turbo | 2.9.4 | medium | direct | 2.9.14 | CVE-2026-45772, CVE-2026-45773 | -| uuid | 10.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| @eslint/plugin-kit | 0.2.8 | low | transitive | 0.3.4 | GHSA-xffm-g5w8-qvg7 | -| @eslint/plugin-kit | 0.3.2 | low | transitive | 0.3.4 | GHSA-xffm-g5w8-qvg7 | -| @tootallnate/once | 1.1.2 | low | transitive | 2.0.1 | CVE-2026-3449 | -| @tootallnate/once | 2.0.0 | low | transitive | 2.0.1 | CVE-2026-3449 | -| elliptic | 6.6.1 | low | transitive | — | CVE-2025-14505 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ---------------------------------------- | --------------------------------------------------- | -------- | ------------ | -------- | --------------------------------------------------- | +| @babel/plugin-transform-modules-systemjs | 7.29.0 | high | transitive | 7.29.4 | CVE-2026-44728 | +| html-minifier | 4.0.0 | high | transitive | — | CVE-2022-37620 | +| minimatch | 8.0.4 | high | transitive | 8.0.6 | CVE-2026-27904, CVE-2026-26996, CVE-2026-27903 | +| minimatch | 9.0.1 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996, CVE-2026-27903 | +| minimatch | 9.0.3 | high | transitive | 9.0.7 | CVE-2026-27904, CVE-2026-26996, CVE-2026-27903 | +| rollup | 2.79.2 | high | transitive | 2.80.0 | CVE-2026-27606 | +| rollup | 4.52.4 | high | transitive | 4.59.0 | CVE-2026-27606 | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | CVE-2026-34043 | +| storybook | 10.1.11 | high | transitive | 10.2.10 | CVE-2026-27148 | +| svgo | 3.3.2 | high | transitive | 3.3.3 | CVE-2026-29074 | +| tmp | 0.2.4 | high | transitive | 0.2.6 | CVE-2026-44705 | +| vite | 8.0.2 | high | transitive | 8.0.5 | CVE-2026-39365, CVE-2026-39363, CVE-2026-39364 | +| xlsx | https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz | high | unknown | — | CVE-2023-30533, CVE-2024-22363 | +| dompurify | 3.1.7 | medium | transitive | 3.4.0 | CVE-2026-41239, CVE-2026-41240, CVE-2026-0540… (+3) | +| element-plus | 2.4.3 | medium | transitive | 2.11.1 | CVE-2025-57665 | +| file-type | 16.5.4 | medium | transitive | 21.3.1 | CVE-2026-31808 | +| ip-address | 9.0.5 | medium | transitive | 10.1.1 | CVE-2026-42338 | +| markdown-it | 13.0.2 | medium | transitive | 14.1.1 | CVE-2026-2327 | +| mjml | 4.15.3 | medium | transitive | 5.0.0 | CVE-2025-67898 | +| nodemailer | 7.0.11 | medium | transitive | 8.0.5 | GHSA-c7w3-x93f-qmm8, GHSA-vvjj-xcjg-gr5g | +| postcss | 8.4.31 | medium | transitive | 8.5.10 | CVE-2026-41305 | +| postcss | 8.5.8 | medium | transitive | 8.5.10 | CVE-2026-41305 | +| prismjs | 1.29.0 | medium | transitive | 1.30.0 | CVE-2024-53382 | +| qs | 6.14.2 | medium | transitive | 6.15.2 | CVE-2026-8723 | +| showdown | 2.1.0 | medium | transitive | — | CVE-2024-1899 | +| turbo | 2.9.4 | medium | direct | 2.9.14 | CVE-2026-45772, CVE-2026-45773 | +| uuid | 10.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| @eslint/plugin-kit | 0.2.8 | low | transitive | 0.3.4 | GHSA-xffm-g5w8-qvg7 | +| @eslint/plugin-kit | 0.3.2 | low | transitive | 0.3.4 | GHSA-xffm-g5w8-qvg7 | +| @tootallnate/once | 1.1.2 | low | transitive | 2.0.1 | CVE-2026-3449 | +| @tootallnate/once | 2.0.0 | low | transitive | 2.0.1 | CVE-2026-3449 | +| elliptic | 6.6.1 | low | transitive | — | CVE-2025-14505 | --- diff --git a/website/docs/case-studies/nestjs.md b/website/docs/case-studies/nestjs.md index ea728401..16083039 100644 --- a/website/docs/case-studies/nestjs.md +++ b/website/docs/case-studies/nestjs.md @@ -33,18 +33,18 @@ The tool also names the parent chain for every transitive finding. `form-data@2. Both tools were run against the same `package-lock.json` on the same machine. -| Metric | npm audit | CVE Lite CLI v1.6.0 | -|---|---:|---:| -| Total reported findings | 36 | 26 | -| Critical | 4 | 1 | -| High | 17 | 8 | -| Moderate / Medium | 13 | 13 | -| Low | 2 | 4 | -| Direct vs transitive breakdown | ✗ | ✓ (1 / 25) | -| Validated fix targets | ✗ | ✓ | -| Breaking change awareness | ✗ | ✓ | -| Parent chain identified for transitive issues | ✗ | ✓ | -| Specific copy-and-run commands | ✗ | ✓ | +| Metric | npm audit | CVE Lite CLI v1.6.0 | +| --------------------------------------------- | --------: | ------------------: | +| Total reported findings | 36 | 26 | +| Critical | 4 | 1 | +| High | 17 | 8 | +| Moderate / Medium | 13 | 13 | +| Low | 2 | 4 | +| Direct vs transitive breakdown | ✗ | ✓ (1 / 25) | +| Validated fix targets | ✗ | ✓ | +| Breaking change awareness | ✗ | ✓ | +| Parent chain identified for transitive issues | ✗ | ✓ | +| Specific copy-and-run commands | ✗ | ✓ | **Why CVE Lite reports fewer findings — and why that is not a coverage gap:** @@ -79,10 +79,10 @@ On a project where 25 of 26 findings are transitive, `npm audit fix` is nearly u Remediation results from the measured workflow documented in this study (specific revision, v1.5.2 scan): -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline | 24 | 0 | 1 | 4 | 19 | 0 | 24 | 1 | -| After measured pass | 21 | 0 | 1 | 3 | 17 | 0 | 21 | 0 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline | 24 | 0 | 1 | 4 | 19 | 0 | 24 | 1 | +| After measured pass | 21 | 0 | 1 | 3 | 17 | 0 | 21 | 0 | The finding count dropped from 24 to 21. The generated command surface dropped from 1 group to 0 — meaning the scanner moved the repository into the deeper transitive-only category where the remaining work belongs to toolchain and parent-chain decisions rather than confident first-pass installs. @@ -174,34 +174,34 @@ This is a useful stopping point for the public study. The scanner surfaced the o Full vulnerable package list at scan time: -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| form-data | 2.3.3 | critical | transitive | 2.5.4 | GHSA-fjxv-7rqg-78g4 | -| fastify | 5.8.4 | high | direct | 5.8.5 | GHSA-247c-9743-5963 | -| diff | 2.2.3 | high | transitive | 3.5.0 | GHSA-73rr-hh4g-fpgx, GHSA-h6ch-v84p-w6p9 | -| braces | 1.8.5 | high | transitive | 3.0.3 | GHSA-grv7-fg5c-xmjg | -| braces | 2.3.2 | high | transitive | 3.0.3 | GHSA-grv7-fg5c-xmjg | -| lodash.template | 3.6.2 | high | transitive | 4.17.21 | GHSA-35jh-r3h4-6jhm | -| glob | 10.4.5 | high | transitive | 10.5.0 | GHSA-5j98-mcp5-4vw2 | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | -| tar | 6.2.1 | high | transitive | 7.5.3 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28c… | -| postcss | 7.0.39 | medium | transitive | 8.4.31 | GHSA-7fh5-64p2-3v2j | -| brace-expansion | 5.0.4 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | -| brace-expansion | 1.1.11 | medium | transitive | 1.1.12 | GHSA-f886-m6hf-6m8v, GHSA-v6h2-p8h4-qcjw | -| brace-expansion | 2.0.2 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | -| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | -| micromatch | 3.1.10 | medium | transitive | 4.0.8 | GHSA-952p-6rrq-rcjv | -| micromatch | 2.3.11 | medium | transitive | 4.0.8 | GHSA-952p-6rrq-rcjv | -| js-yaml | 3.14.1 | medium | transitive | 3.14.2 | GHSA-mh29-5h37-fv8m | -| js-yaml | 4.1.0 | medium | transitive | 3.14.2 | GHSA-mh29-5h37-fv8m | -| request | 2.88.2 | medium | transitive | 3.0.0 | GHSA-p8p7-x288-28g6 | -| qs | 6.5.3 | medium | transitive | 6.14.1 | GHSA-6rw7-vpxm-498p | -| tough-cookie | 2.5.0 | medium | transitive | 4.1.3 | GHSA-72xf-g2v4-qvf3 | -| yaml | 2.8.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | -| @tootallnate/once | 1.1.2 | low | transitive | 3.0.1 | GHSA-vpq2-c234-7xj6 | -| diff | 4.0.2 | low | transitive | 3.5.1 | GHSA-73rr-hh4g-fpgx | -| diff | 7.0.0 | low | transitive | 3.5.1 | GHSA-73rr-hh4g-fpgx | -| qs | 6.14.1 | low | transitive | 6.14.2 | GHSA-w7fw-mjwx-w883 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| -------------------- | ------- | -------- | ------------ | -------- | ---------------------------------------- | +| form-data | 2.3.3 | critical | transitive | 2.5.4 | GHSA-fjxv-7rqg-78g4 | +| fastify | 5.8.4 | high | direct | 5.8.5 | GHSA-247c-9743-5963 | +| diff | 2.2.3 | high | transitive | 3.5.0 | GHSA-73rr-hh4g-fpgx, GHSA-h6ch-v84p-w6p9 | +| braces | 1.8.5 | high | transitive | 3.0.3 | GHSA-grv7-fg5c-xmjg | +| braces | 2.3.2 | high | transitive | 3.0.3 | GHSA-grv7-fg5c-xmjg | +| lodash.template | 3.6.2 | high | transitive | 4.17.21 | GHSA-35jh-r3h4-6jhm | +| glob | 10.4.5 | high | transitive | 10.5.0 | GHSA-5j98-mcp5-4vw2 | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | +| tar | 6.2.1 | high | transitive | 7.5.3 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28c… | +| postcss | 7.0.39 | medium | transitive | 8.4.31 | GHSA-7fh5-64p2-3v2j | +| brace-expansion | 5.0.4 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | +| brace-expansion | 1.1.11 | medium | transitive | 1.1.12 | GHSA-f886-m6hf-6m8v, GHSA-v6h2-p8h4-qcjw | +| brace-expansion | 2.0.2 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | +| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | +| micromatch | 3.1.10 | medium | transitive | 4.0.8 | GHSA-952p-6rrq-rcjv | +| micromatch | 2.3.11 | medium | transitive | 4.0.8 | GHSA-952p-6rrq-rcjv | +| js-yaml | 3.14.1 | medium | transitive | 3.14.2 | GHSA-mh29-5h37-fv8m | +| js-yaml | 4.1.0 | medium | transitive | 3.14.2 | GHSA-mh29-5h37-fv8m | +| request | 2.88.2 | medium | transitive | 3.0.0 | GHSA-p8p7-x288-28g6 | +| qs | 6.5.3 | medium | transitive | 6.14.1 | GHSA-6rw7-vpxm-498p | +| tough-cookie | 2.5.0 | medium | transitive | 4.1.3 | GHSA-72xf-g2v4-qvf3 | +| yaml | 2.8.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | +| @tootallnate/once | 1.1.2 | low | transitive | 3.0.1 | GHSA-vpq2-c234-7xj6 | +| diff | 4.0.2 | low | transitive | 3.5.1 | GHSA-73rr-hh4g-fpgx | +| diff | 7.0.0 | low | transitive | 3.5.1 | GHSA-73rr-hh4g-fpgx | +| qs | 6.14.1 | low | transitive | 6.14.2 | GHSA-w7fw-mjwx-w883 | ## Want your project reviewed? diff --git a/website/docs/case-studies/openai-agents-js.md b/website/docs/case-studies/openai-agents-js.md index 5e82840f..12a1f106 100644 --- a/website/docs/case-studies/openai-agents-js.md +++ b/website/docs/case-studies/openai-agents-js.md @@ -61,18 +61,18 @@ The output documents the full chain: `project → verdaccio → @verdaccio/local Both tools were run against the same `pnpm-lock.yaml` on the same machine on 2026-05-30 (Node.js 22+, pnpm 10.14.0). -| Metric | pnpm audit (10.14.0) | CVE Lite CLI v1.18.1 | -|---|---:|---:| -| Total reported findings | 52 | 31 | -| Critical | 0 | 0 | -| High | 27 | 13 | -| Moderate / Medium | 22 | 16 | -| Low | 3 | 2 | -| Direct vs transitive breakdown | ✗ | ✓ (0 / 31) | -| Deduplicated package view | ✗ | ✓ | -| Parent package named per finding | partial (paths) | ✓ | -| Specific copy-and-run commands | partial | ✓ (1 group) | -| Skipped findings with reason | ✗ | ✓ (30 entries) | +| Metric | pnpm audit (10.14.0) | CVE Lite CLI v1.18.1 | +| -------------------------------- | -------------------: | -------------------: | +| Total reported findings | 52 | 31 | +| Critical | 0 | 0 | +| High | 27 | 13 | +| Moderate / Medium | 22 | 16 | +| Low | 3 | 2 | +| Direct vs transitive breakdown | ✗ | ✓ (0 / 31) | +| Deduplicated package view | ✗ | ✓ | +| Parent package named per finding | partial (paths) | ✓ | +| Specific copy-and-run commands | partial | ✓ (1 group) | +| Skipped findings with reason | ✗ | ✓ (30 entries) | **Why the totals differ:** @@ -90,11 +90,11 @@ Both tools were run against the same `pnpm-lock.yaml` on the same machine on 202 No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 31 | 0 | 13 | 16 | 2 | 0 | 31 | 1 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 31 | 0 | 13 | 16 | 2 | 0 | 31 | 1 | -One command group covering one finding is a **realistic outcome** for an all-transitive AI SDK monorepo — the case study documents *where triage time goes* (parent routing), not a false promise of 31 one-liner fixes. +One command group covering one finding is a **realistic outcome** for an all-transitive AI SDK monorepo — the case study documents _where triage time goes_ (parent routing), not a false promise of 31 one-liner fixes. --- @@ -157,20 +157,20 @@ The example lockfile reflects the SDK at revision `f76fc19fba03dfbecf34ffd923025 Every number in this case study comes from a live scan of the committed fixture at `examples/openai-agents-js/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-05-30 | -| CLI version | v1.18.1 | -| CVE Lite command | `node dist/index.js examples/openai-agents-js --verbose --all --json` | -| pnpm audit command | `pnpm audit` / `pnpm audit --json` (Node.js 22+, pnpm 10.14.0) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/openai-agents-js/pnpm-lock.yaml` from [openai/openai-agents-js@f76fc19](https://github.com/openai/openai-agents-js/commit/f76fc19fba03dfbecf34ffd92302543b3b1d4890) | -| Packages parsed (CVE Lite) | 1,683 | -| Unique vulnerable packages (CVE Lite) | 31 | -| Vulnerability entries (pnpm audit) | 52 | -| Fix command groups (CVE Lite) | 1 | -| First-pass covered findings (CVE Lite) | 1 | -| Skipped findings with reason (CVE Lite) | 30 | +| Field | Value | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-05-30 | +| CLI version | v1.18.1 | +| CVE Lite command | `node dist/index.js examples/openai-agents-js --verbose --all --json` | +| pnpm audit command | `pnpm audit` / `pnpm audit --json` (Node.js 22+, pnpm 10.14.0) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/openai-agents-js/pnpm-lock.yaml` from [openai/openai-agents-js@f76fc19](https://github.com/openai/openai-agents-js/commit/f76fc19fba03dfbecf34ffd92302543b3b1d4890) | +| Packages parsed (CVE Lite) | 1,683 | +| Unique vulnerable packages (CVE Lite) | 31 | +| Vulnerability entries (pnpm audit) | 52 | +| Fix command groups (CVE Lite) | 1 | +| First-pass covered findings (CVE Lite) | 1 | +| Skipped findings with reason (CVE Lite) | 30 | Reproduce CVE Lite locally from the repository root: @@ -208,39 +208,39 @@ All 31 baseline findings remain open at the time of this study. No remediation w Full vulnerable package list from the verified scan on 2026-05-30 (revision `f76fc19`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| @hono/node-server | 1.19.9 | high | transitive | 1.19.13 | CVE-2026-39406, CVE-2026-29087 | -| @opentelemetry/exporter-prometheus | 0.207.0 | high | transitive | 0.217.0 | CVE-2026-44902 | -| @opentelemetry/sdk-node | 0.207.0 | high | transitive | 0.217.0 | CVE-2026-44902 | -| axios | 1.15.1 | high | transitive | 1.16.0 | CVE-2026-44494, CVE-2026-44495… | -| devalue | 5.7.1 | high | transitive | 5.8.1 | CVE-2026-42570 | -| effect | 3.18.4 | high | transitive | 3.20.0 | CVE-2026-32887 | -| express-rate-limit | 8.2.1 | high | transitive | 8.2.2 | CVE-2026-30827 | -| fast-uri | 3.1.0 | high | transitive | 3.1.2 | CVE-2026-6321, CVE-2026-6322 | -| fast-xml-builder | 1.1.5 | high | transitive | 1.1.7 | CVE-2026-44664, CVE-2026-44665 | -| minimatch | 7.4.6 | high | transitive | 7.4.8 | CVE-2026-27904, CVE-2026-26996… | -| path-to-regexp | 8.3.0 | high | transitive | 8.4.0 | CVE-2026-4923, CVE-2026-4926 | -| protobufjs | 7.5.5 | high | transitive | 7.5.8 | CVE-2026-44294, CVE-2026-44293… | -| tar | 7.4.3 | high | transitive | 7.5.11 | CVE-2026-24842, CVE-2026-26960… | -| @protobufjs/utf8 | 1.1.0 | medium | transitive | 1.1.1 | CVE-2026-44288 | -| ajv | 8.17.1 | medium | transitive | 8.18.0 | CVE-2025-69873 | -| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | CVE-2026-33750 | -| brace-expansion | 5.0.4 | medium | transitive | 5.0.6 | CVE-2026-33750, CVE-2026-45149 | -| brace-expansion | 5.0.5 | medium | transitive | 5.0.6 | CVE-2026-45149 | -| fast-xml-parser | 5.5.8 | medium | transitive | 5.7.0 | CVE-2026-41650 | -| hono | 4.12.16 | medium | transitive | 4.12.18 | CVE-2026-44459, CVE-2026-44457… | -| ip-address | 10.0.1 | medium | transitive | 10.1.1 | CVE-2026-42338 | -| postcss | 8.4.31 | medium | transitive | 8.5.10 | CVE-2026-41305 | -| postcss | 8.5.5 | medium | transitive | 8.5.10 | CVE-2026-41305 | -| postcss | 8.5.8 | medium | transitive | 8.5.10 | CVE-2026-41305 | -| qs | 6.14.1 | medium | transitive | 6.15.2 | CVE-2026-8723, CVE-2026-2391 | -| qs | 6.14.2 | medium | transitive | 6.15.2 | CVE-2026-8723 | -| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 11.1.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| ws | 8.18.2 | medium | transitive | 8.20.1 | CVE-2026-45736 | -| @ai-sdk/provider-utils | 2.2.8 | low | transitive | 4.0.0 | CVE-2026-8769 | -| @ai-sdk/provider-utils | 3.0.3 | low | transitive | 4.0.0 | CVE-2026-8769 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ---------------------------------- | ------- | -------- | ------------ | -------- | ------------------------------- | +| @hono/node-server | 1.19.9 | high | transitive | 1.19.13 | CVE-2026-39406, CVE-2026-29087 | +| @opentelemetry/exporter-prometheus | 0.207.0 | high | transitive | 0.217.0 | CVE-2026-44902 | +| @opentelemetry/sdk-node | 0.207.0 | high | transitive | 0.217.0 | CVE-2026-44902 | +| axios | 1.15.1 | high | transitive | 1.16.0 | CVE-2026-44494, CVE-2026-44495… | +| devalue | 5.7.1 | high | transitive | 5.8.1 | CVE-2026-42570 | +| effect | 3.18.4 | high | transitive | 3.20.0 | CVE-2026-32887 | +| express-rate-limit | 8.2.1 | high | transitive | 8.2.2 | CVE-2026-30827 | +| fast-uri | 3.1.0 | high | transitive | 3.1.2 | CVE-2026-6321, CVE-2026-6322 | +| fast-xml-builder | 1.1.5 | high | transitive | 1.1.7 | CVE-2026-44664, CVE-2026-44665 | +| minimatch | 7.4.6 | high | transitive | 7.4.8 | CVE-2026-27904, CVE-2026-26996… | +| path-to-regexp | 8.3.0 | high | transitive | 8.4.0 | CVE-2026-4923, CVE-2026-4926 | +| protobufjs | 7.5.5 | high | transitive | 7.5.8 | CVE-2026-44294, CVE-2026-44293… | +| tar | 7.4.3 | high | transitive | 7.5.11 | CVE-2026-24842, CVE-2026-26960… | +| @protobufjs/utf8 | 1.1.0 | medium | transitive | 1.1.1 | CVE-2026-44288 | +| ajv | 8.17.1 | medium | transitive | 8.18.0 | CVE-2025-69873 | +| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | CVE-2026-33750 | +| brace-expansion | 5.0.4 | medium | transitive | 5.0.6 | CVE-2026-33750, CVE-2026-45149 | +| brace-expansion | 5.0.5 | medium | transitive | 5.0.6 | CVE-2026-45149 | +| fast-xml-parser | 5.5.8 | medium | transitive | 5.7.0 | CVE-2026-41650 | +| hono | 4.12.16 | medium | transitive | 4.12.18 | CVE-2026-44459, CVE-2026-44457… | +| ip-address | 10.0.1 | medium | transitive | 10.1.1 | CVE-2026-42338 | +| postcss | 8.4.31 | medium | transitive | 8.5.10 | CVE-2026-41305 | +| postcss | 8.5.5 | medium | transitive | 8.5.10 | CVE-2026-41305 | +| postcss | 8.5.8 | medium | transitive | 8.5.10 | CVE-2026-41305 | +| qs | 6.14.1 | medium | transitive | 6.15.2 | CVE-2026-8723, CVE-2026-2391 | +| qs | 6.14.2 | medium | transitive | 6.15.2 | CVE-2026-8723 | +| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 11.1.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| ws | 8.18.2 | medium | transitive | 8.20.1 | CVE-2026-45736 | +| @ai-sdk/provider-utils | 2.2.8 | low | transitive | 4.0.0 | CVE-2026-8769 | +| @ai-sdk/provider-utils | 3.0.3 | low | transitive | 4.0.0 | CVE-2026-8769 | --- diff --git a/website/docs/case-studies/owasp-juice-shop.md b/website/docs/case-studies/owasp-juice-shop.md index fc1c6e05..7bf3d196 100644 --- a/website/docs/case-studies/owasp-juice-shop.md +++ b/website/docs/case-studies/owasp-juice-shop.md @@ -33,18 +33,18 @@ On transitive findings, it names the parent chain — for example, `crypto-js` i Both tools were run against the same `package-lock.json` on the same machine. -| Metric | npm audit | CVE Lite CLI v1.6.0 | -|---|---:|---:| -| Total reported findings | 55 | 19 | -| Critical | 7 | 3 | -| High | 31 | 10 | -| Moderate / Medium | 11 | 4 | -| Low | 6 | 2 | -| Direct vs transitive breakdown | ✗ | ✓ (4 / 15) | -| Validated fix targets | ✗ | ✓ | -| Breaking change awareness | ✗ | ✓ | -| Parent chain identified for transitive issues | ✗ | ✓ | -| Specific copy-and-run commands | ✗ | ✓ | +| Metric | npm audit | CVE Lite CLI v1.6.0 | +| --------------------------------------------- | --------: | ------------------: | +| Total reported findings | 55 | 19 | +| Critical | 7 | 3 | +| High | 31 | 10 | +| Moderate / Medium | 11 | 4 | +| Low | 6 | 2 | +| Direct vs transitive breakdown | ✗ | ✓ (4 / 15) | +| Validated fix targets | ✗ | ✓ | +| Breaking change awareness | ✗ | ✓ | +| Parent chain identified for transitive issues | ✗ | ✓ | +| Specific copy-and-run commands | ✗ | ✓ | **Why CVE Lite reports fewer findings — and why that is not a coverage gap:** @@ -81,11 +81,11 @@ Each command is a validated non-vulnerable target. `npm audit fix --force` is a Remediation results from the measured workflow documented in this study (earlier revision, v1.5.2): -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline | 39 | 3 | 1 | 11 | 23 | 10 | 29 | 6 | -| After first direct pass | 27 | 1 | 0 | 10 | 16 | 4 | 23 | 3 | -| After second pass | 18 | 1 | 0 | 5 | 12 | 3 | 15 | 1 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ----------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline | 39 | 3 | 1 | 11 | 23 | 10 | 29 | 6 | +| After first direct pass | 27 | 1 | 0 | 10 | 16 | 4 | 23 | 3 | +| After second pass | 18 | 1 | 0 | 5 | 12 | 3 | 15 | 1 | The finding count dropped from 39 to 18. Critical findings dropped from 3 to 1. The single high-severity finding was cleared. The command surface dropped from 6 groups to 1 — meaning the scanner moved the project through the first two actionable passes and made the remaining blockers explicit rather than mixing them with fixable noise. @@ -160,27 +160,27 @@ This is the part of remediation where a maintainer stops asking "what can I bump Full vulnerable package list at scan time: -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| crypto-js | 3.3.0 | critical | transitive | 4.2.0 | GHSA-xwcq-pm8m-c4vf | -| marsdb | 0.6.11 | critical | direct | — | GHSA-5mrr-rgp6-x4gr | -| vm2 | 3.9.17 | critical | transitive | 3.9.18 | GHSA-99p7-6v5w-7xg8, GHSA-cchq-frgv-rjh… | -| braces | 2.3.2 | high | transitive | 3.0.3 | GHSA-grv7-fg5c-xmjg | -| jsonwebtoken | 8.5.1 | high | direct | 9.0.0 | GHSA-8cf7-32gw-wr33, GHSA-hjrf-2m68-595… | -| lodash | 4.17.23 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | -| minimatch | 3.0.8 | high | transitive | 3.1.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2… | -| http-cache-semantics | 3.8.1 | high | transitive | 4.1.1 | GHSA-rc47-6667-2j5j | -| lodash.set | 4.3.2 | high | transitive | 4.17.19 | GHSA-p6mc-m468-83gw | -| minimatch | 9.0.3 | high | transitive | 3.1.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2… | -| tar | 4.4.19 | high | transitive | 6.2.1 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28c… | -| minimatch | 3.0.5 | high | transitive | 3.1.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2… | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | -| got | 8.3.2 | medium | transitive | 11.8.5 | GHSA-pfrx-2q88-qq97 | -| micromatch | 3.1.10 | medium | transitive | 4.0.8 | GHSA-952p-6rrq-rcjv | -| notevil | 1.3.3 | medium | direct | — | GHSA-8g4m-cjm2-96wq | -| sanitize-html | 2.17.2 | medium | direct | 2.17.3 | GHSA-9mrh-v2v3-xpfm | -| @tootallnate/once | 2.0.0 | low | transitive | 3.0.1 | GHSA-vpq2-c234-7xj6 | -| messageformat | 2.3.0 | low | transitive | 3.0.0-beta.0 | GHSA-xfqm-j7pc-xrfc | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| -------------------- | ------- | -------- | ------------ | ------------ | ---------------------------------------- | +| crypto-js | 3.3.0 | critical | transitive | 4.2.0 | GHSA-xwcq-pm8m-c4vf | +| marsdb | 0.6.11 | critical | direct | — | GHSA-5mrr-rgp6-x4gr | +| vm2 | 3.9.17 | critical | transitive | 3.9.18 | GHSA-99p7-6v5w-7xg8, GHSA-cchq-frgv-rjh… | +| braces | 2.3.2 | high | transitive | 3.0.3 | GHSA-grv7-fg5c-xmjg | +| jsonwebtoken | 8.5.1 | high | direct | 9.0.0 | GHSA-8cf7-32gw-wr33, GHSA-hjrf-2m68-595… | +| lodash | 4.17.23 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | +| minimatch | 3.0.8 | high | transitive | 3.1.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2… | +| http-cache-semantics | 3.8.1 | high | transitive | 4.1.1 | GHSA-rc47-6667-2j5j | +| lodash.set | 4.3.2 | high | transitive | 4.17.19 | GHSA-p6mc-m468-83gw | +| minimatch | 9.0.3 | high | transitive | 3.1.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2… | +| tar | 4.4.19 | high | transitive | 6.2.1 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28c… | +| minimatch | 3.0.5 | high | transitive | 3.1.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2… | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | +| got | 8.3.2 | medium | transitive | 11.8.5 | GHSA-pfrx-2q88-qq97 | +| micromatch | 3.1.10 | medium | transitive | 4.0.8 | GHSA-952p-6rrq-rcjv | +| notevil | 1.3.3 | medium | direct | — | GHSA-8g4m-cjm2-96wq | +| sanitize-html | 2.17.2 | medium | direct | 2.17.3 | GHSA-9mrh-v2v3-xpfm | +| @tootallnate/once | 2.0.0 | low | transitive | 3.0.1 | GHSA-vpq2-c234-7xj6 | +| messageformat | 2.3.0 | low | transitive | 3.0.0-beta.0 | GHSA-xfqm-j7pc-xrfc | ## Want your project reviewed? diff --git a/website/docs/case-studies/payload.md b/website/docs/case-studies/payload.md index de984590..679e53a3 100644 --- a/website/docs/case-studies/payload.md +++ b/website/docs/case-studies/payload.md @@ -42,19 +42,19 @@ Unlike raw advisory feeds, CVE Lite identifies a clear distinction between direc Both tools were run against the same `pnpm-lock.yaml` on the same machine on 2026-06-12. -| Metric | pnpm audit | CVE Lite CLI | -|---|---:|---:| -| Total reported findings | 42 | 18 | -| Critical | 1 | 1 | -| High | 12 | 7 | -| Moderate / Medium | 26 | 9 | -| Low | 3 | 1 | -| Direct vs transitive breakdown | ✗ | ✓ (1 / 17) | -| Deduplicated package view | ✗ | ✓ | -| Validated fix targets | ✗ | ✓ | -| Parent-upgrade guidance | ✗ | ✓ | -| Copy-and-run commands | ✗ | ✓ (2 groups) | -| Skipped findings with reason | ✗ | ✓ | +| Metric | pnpm audit | CVE Lite CLI | +| ------------------------------ | ---------: | -----------: | +| Total reported findings | 42 | 18 | +| Critical | 1 | 1 | +| High | 12 | 7 | +| Moderate / Medium | 26 | 9 | +| Low | 3 | 1 | +| Direct vs transitive breakdown | ✗ | ✓ (1 / 17) | +| Deduplicated package view | ✗ | ✓ | +| Validated fix targets | ✗ | ✓ | +| Parent-upgrade guidance | ✗ | ✓ | +| Copy-and-run commands | ✗ | ✓ (2 groups) | +| Skipped findings with reason | ✗ | ✓ | ### Why the totals differ @@ -89,10 +89,10 @@ For the critical finding, CVE Lite names `fast-xml-parser` as requiring manual c A remediation attempt was performed using the generated workspace-scoped `drizzle-orm` upgrade command. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 18 | 1 | 7 | 9 | 1 | 1 | 17 | 2 | -| After remediation attempt | 18 | 1 | 7 | 9 | 1 | 1 | 17 | 2 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 18 | 1 | 7 | 9 | 1 | 1 | 17 | 2 | +| After remediation attempt | 18 | 1 | 7 | 9 | 1 | 1 | 17 | 2 | The generated remediation command completed successfully but did not reduce the finding count. The vulnerable `drizzle-orm@0.44.7` version remained present in the dependency graph. @@ -164,18 +164,18 @@ The example lockfile in this repository reflects Payload CMS at revision `eb5708 Every number in this case study comes from a live scan of the committed fixture at `examples/payload/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-06-12 | -| CLI version | v1.22.0 | -| CVE Lite command | `npx cve-lite-cli . --verbose --all` | -| pnpm audit command | `pnpm audit` | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/payload/pnpm-lock.yaml` from [payloadcms/payload@eb5708b](https://github.com/payloadcms/payload/commit/eb5708b3d3834a50a29f19d93df7406011e08114) | -| Packages parsed (CVE Lite) | 2,602 | -| Unique vulnerable packages (CVE Lite) | 18 | -| Vulnerability entries (pnpm audit) | 42 | -| Fix command groups (CVE Lite) | 2 | +| Field | Value | +| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-06-12 | +| CLI version | v1.22.0 | +| CVE Lite command | `npx cve-lite-cli . --verbose --all` | +| pnpm audit command | `pnpm audit` | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/payload/pnpm-lock.yaml` from [payloadcms/payload@eb5708b](https://github.com/payloadcms/payload/commit/eb5708b3d3834a50a29f19d93df7406011e08114) | +| Packages parsed (CVE Lite) | 2,602 | +| Unique vulnerable packages (CVE Lite) | 18 | +| Vulnerability entries (pnpm audit) | 42 | +| Fix command groups (CVE Lite) | 2 | Reproduce CVE Lite locally from the repository root: @@ -212,9 +212,11 @@ All 18 findings remain present after the remediation attempt. The verified scan surfaced 18 vulnerable packages: ### Critical + - fast-xml-parser@4.2.5 ### High + - drizzle-orm@0.44.7 (direct) - undici@7.18.2 - rollup@3.29.5 @@ -223,6 +225,7 @@ The verified scan surfaced 18 vulnerable packages: - @opennextjs/cloudflare@1.16.1 ### Medium + - file-type@20.5.0 - yaml@2.4.5 - postcss@8.4.31 @@ -235,6 +238,7 @@ The verified scan surfaced 18 vulnerable packages: - and additional transitive findings ### Low + - One transitive low-severity finding ## Want your project reviewed? @@ -247,4 +251,4 @@ Please include: - why the project would make a useful case study - whether the dependency graph is publicly reproducible -Not every project will be selected. Preference will go to projects that are publicly useful, technically interesting, and strong examples of realistic dependency remediation workflows. \ No newline at end of file +Not every project will be selected. Preference will go to projects that are publicly useful, technically interesting, and strong examples of realistic dependency remediation workflows. diff --git a/website/docs/case-studies/presenton.md b/website/docs/case-studies/presenton.md index 11ee930c..8e7bbffb 100644 --- a/website/docs/case-studies/presenton.md +++ b/website/docs/case-studies/presenton.md @@ -27,11 +27,11 @@ Presenton ships **two independent npm dependency graphs**: -| Lockfile | Role | Packages | Findings | Command groups | -|---|---|---:|---:|---:| -| Root `package-lock.json` | Monorepo orchestrator / workspace tooling | 93 | 1 | 1 | -| `electron/package-lock.json` | Electron desktop application shell | 501 | 8 | 4 | -| **Combined** | JavaScript lockfiles only | **594** | **9** | **5** | +| Lockfile | Role | Packages | Findings | Command groups | +| ---------------------------- | ----------------------------------------- | -------: | -------: | -------------: | +| Root `package-lock.json` | Monorepo orchestrator / workspace tooling | 93 | 1 | 1 | +| `electron/package-lock.json` | Electron desktop application shell | 501 | 8 | 4 | +| **Combined** | JavaScript lockfiles only | **594** | **9** | **5** | The repository also contains a **Python/FastAPI** server under `servers/`. That stack is **not included** in this fixture or case study — only the committed npm lockfiles above. @@ -84,24 +84,24 @@ Both tools were run against each lockfile on the same machine on 2026-06-09. ### Root `package-lock.json` -| Metric | npm audit | CVE Lite CLI v1.20.0 | -|---|---:|---:| -| Packages parsed | 93 | 93 | -| Total reported findings | 1 | 1 | -| High | 1 | 1 | -| Direct vs transitive breakdown | ✗ | ✓ (0 / 1) | -| Within-range refresh command | partial | ✓ (`npm update @llamaindex/liteparse`) | +| Metric | npm audit | CVE Lite CLI v1.20.0 | +| ------------------------------ | --------: | -------------------------------------: | +| Packages parsed | 93 | 93 | +| Total reported findings | 1 | 1 | +| High | 1 | 1 | +| Direct vs transitive breakdown | ✗ | ✓ (0 / 1) | +| Within-range refresh command | partial | ✓ (`npm update @llamaindex/liteparse`) | ### Electron `package-lock.json` -| Metric | npm audit | CVE Lite CLI v1.20.0 | -|---|---:|---:| -| Packages parsed | 501 | 501 | -| Total reported findings | 8 | 8 | -| High | 5 | 5 | -| Moderate / Medium | 3 | 3 | -| Direct vs transitive breakdown | ✗ | ✓ (1 / 7) | -| Copy-and-run command groups | partial (`npm audit fix`) | ✓ (4 groups) | +| Metric | npm audit | CVE Lite CLI v1.20.0 | +| ------------------------------ | ------------------------: | -------------------: | +| Packages parsed | 501 | 501 | +| Total reported findings | 8 | 8 | +| High | 5 | 5 | +| Moderate / Medium | 3 | 3 | +| Direct vs transitive breakdown | ✗ | ✓ (1 / 7) | +| Copy-and-run command groups | partial (`npm audit fix`) | ✓ (4 groups) | **Why the totals align:** On both lockfiles, npm audit and CVE Lite report the **same deduplicated package counts** (1 root, 8 electron). CVE Lite's value is remediation specificity — separating `npm update @llamaindex/liteparse` (within-range axios refresh) from `npm install electron-builder@26.8.2` (partial path coverage on `@isaacs/brace-expansion`) and `npm install uuid@13.0.1` (direct fix), rather than a single blunt `npm audit fix`. @@ -111,11 +111,11 @@ Both tools were run against each lockfile on the same machine on 2026-06-09. No remediation pass was performed for this study. -| Lockfile | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Root | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | -| Electron | 8 | 0 | 5 | 3 | 0 | 1 | 7 | 4 | -| **Combined** | **9** | **0** | **6** | **3** | **0** | **1** | **8** | **5** | +| Lockfile | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------ | -------: | -------: | ----: | -----: | ----: | -----: | ---------: | -------------: | +| Root | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | +| Electron | 8 | 0 | 5 | 3 | 0 | 1 | 7 | 4 | +| **Combined** | **9** | **0** | **6** | **3** | **0** | **1** | **8** | **5** | All **9 of 9** findings have first-pass copy-and-run commands — the ideal outcome for a lean AI-app graph split across two lockfiles. @@ -172,19 +172,19 @@ The example lockfiles reflect Presenton at revision `493aff5c764c13f7249a9a908fe Every number in this case study comes from live scans of the committed fixtures in the CVE Lite CLI repository. -| Field | Root | Electron | -|---|---|---| -| Scan date | 2026-06-09 | 2026-06-09 | -| CLI version | v1.20.0 | v1.20.0 | -| CVE Lite command | `node dist/index.js examples/presenton --verbose --all --json` | `node dist/index.js examples/presenton/electron --verbose --all --json` | -| npm audit | 1 high | 5 high · 3 moderate (8 total) | -| Advisory source | OSV (online) | OSV (online) | -| Lockfile source | [presenton/presenton@493aff5](https://github.com/presenton/presenton/commit/493aff5c764c13f7249a9a908fe41aa85c19b7c3) | same revision | -| Packages parsed | 93 | 501 | -| Unique vulnerable packages | 1 | 8 | -| OSV advisory matches | 8 | 31 | -| Fix command groups | 1 | 4 | -| First-pass coverage | 1 / 1 | 8 / 8 | +| Field | Root | Electron | +| -------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| Scan date | 2026-06-09 | 2026-06-09 | +| CLI version | v1.20.0 | v1.20.0 | +| CVE Lite command | `node dist/index.js examples/presenton --verbose --all --json` | `node dist/index.js examples/presenton/electron --verbose --all --json` | +| npm audit | 1 high | 5 high · 3 moderate (8 total) | +| Advisory source | OSV (online) | OSV (online) | +| Lockfile source | [presenton/presenton@493aff5](https://github.com/presenton/presenton/commit/493aff5c764c13f7249a9a908fe41aa85c19b7c3) | same revision | +| Packages parsed | 93 | 501 | +| Unique vulnerable packages | 1 | 8 | +| OSV advisory matches | 8 | 31 | +| Fix command groups | 1 | 4 | +| First-pass coverage | 1 / 1 | 8 / 8 | Reproduce locally: @@ -201,9 +201,11 @@ node dist/index.js examples/presenton/electron --verbose --all All 9 baseline findings remain open at the time of this study. No remediation was applied. **Root (1 finding):** + - **1 high:** `axios@1.15.2` (transitive via `@llamaindex/liteparse`) **Electron (8 findings):** + - **5 high:** `@isaacs/brace-expansion`, two `minimatch` versions, `axios`, `tmp` - **3 medium:** direct `uuid`, `brace-expansion`, `follow-redirects` @@ -213,22 +215,22 @@ All 9 baseline findings remain open at the time of this study. No remediation wa ### Root lockfile (`examples/presenton/`) -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| axios | 1.15.2 | high | transitive | 1.16.0 | GHSA-35jp-ww65-95wh, GHSA-654m-c8p4-x5fp | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ------- | ------- | -------- | ------------ | -------- | ---------------------------------------- | +| axios | 1.15.2 | high | transitive | 1.16.0 | GHSA-35jp-ww65-95wh, GHSA-654m-c8p4-x5fp | ### Electron lockfile (`examples/presenton/electron/`) -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| @isaacs/brace-expansion | 5.0.0 | high | transitive | 5.0.1 | GHSA-7h2j-956f-4vf2 | -| axios | 1.14.0 | high | transitive | 1.16.0 | GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw | -| minimatch | 3.1.2 | high | transitive | 3.1.4 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 10.1.1 | high | transitive | 10.2.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| tmp | 0.2.5 | high | transitive | 0.2.6 | GHSA-ph9p-34f9-6g65 | -| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | -| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | -| uuid | 13.0.0 | medium | direct | 13.0.1 | GHSA-w5hq-g745-h8pq | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ----------------------- | ------- | -------- | ------------ | -------- | ---------------------------------------- | +| @isaacs/brace-expansion | 5.0.0 | high | transitive | 5.0.1 | GHSA-7h2j-956f-4vf2 | +| axios | 1.14.0 | high | transitive | 1.16.0 | GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw | +| minimatch | 3.1.2 | high | transitive | 3.1.4 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 10.1.1 | high | transitive | 10.2.3 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| tmp | 0.2.5 | high | transitive | 0.2.6 | GHSA-ph9p-34f9-6g65 | +| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | +| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | +| uuid | 13.0.0 | medium | direct | 13.0.1 | GHSA-w5hq-g745-h8pq | --- diff --git a/website/docs/case-studies/storybook.md b/website/docs/case-studies/storybook.md index fc133dd6..281721da 100644 --- a/website/docs/case-studies/storybook.md +++ b/website/docs/case-studies/storybook.md @@ -51,19 +51,19 @@ That command addresses the high-severity `vite@7.2.2` finding. The remaining **9 Both tools were run against the same `yarn.lock` on the same machine on 2026-05-27. -| Metric | yarn npm audit (4.10.3, default) | CVE Lite CLI v1.18.0 | -|---|---:|---:| -| Total reported findings | 3 | 92 | -| Critical | 0 | 5 | -| High | 2 | 40 | -| Moderate / Medium | 1 | 39 | -| Low | 0 | 8 | -| Direct vs transitive breakdown | ✗ | ✓ (2 / 90) | -| Full lockfile package parse | ✗ (root workspace direct deps only) | ✓ (3,008 packages) | -| Deduplicated package view | ✗ | ✓ | -| Validated fix targets | partial | ✓ | -| Specific copy-and-run commands | ✗ | ✓ (1 group) | -| Skipped findings with reason | ✗ | ✓ (90 entries) | +| Metric | yarn npm audit (4.10.3, default) | CVE Lite CLI v1.18.0 | +| ------------------------------ | ----------------------------------: | -------------------: | +| Total reported findings | 3 | 92 | +| Critical | 0 | 5 | +| High | 2 | 40 | +| Moderate / Medium | 1 | 39 | +| Low | 0 | 8 | +| Direct vs transitive breakdown | ✗ | ✓ (2 / 90) | +| Full lockfile package parse | ✗ (root workspace direct deps only) | ✓ (3,008 packages) | +| Deduplicated package view | ✗ | ✓ | +| Validated fix targets | partial | ✓ | +| Specific copy-and-run commands | ✗ | ✓ (1 group) | +| Skipped findings with reason | ✗ | ✓ (90 entries) | **Why the totals differ — and why that is not a coverage gap:** @@ -83,9 +83,9 @@ Running `yarn npm audit -AR` on this fixture without a full monorepo install doe No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 92 | 5 | 40 | 39 | 8 | 2 | 90 | 1 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 92 | 5 | 40 | 39 | 8 | 2 | 90 | 1 | The first-pass plan covers **1 of 92** findings. The remaining **91** appear in the skipped section — overwhelmingly transitive packages where Yarn Berry path reconstruction is limited in this MVP, or where no safe parent upgrade was identified automatically. @@ -133,19 +133,19 @@ The example lockfile reflects Storybook at revision `cc19ae1a2145e8f7cda8dc869f1 Every number in this case study comes from a live scan of the committed fixture at `examples/storybook/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-05-27 | -| CLI version | v1.18.0 | -| CVE Lite command | `npx tsx src/index.ts examples/storybook --json --all` | -| yarn audit command | `yarn npm audit` (Yarn 4.10.3, default direct-deps scope) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/storybook/yarn.lock` from [storybookjs/storybook@cc19ae1](https://github.com/storybookjs/storybook/commit/cc19ae1a2145e8f7cda8dc869f1b90d5346dcedb) | -| Packages parsed (CVE Lite) | 3,008 | -| Unique vulnerable packages (CVE Lite) | 92 | -| Vulnerability entries (yarn npm audit) | 3 | -| Fix command groups (CVE Lite) | 1 | -| Skipped findings with reason (CVE Lite) | 90 | +| Field | Value | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-05-27 | +| CLI version | v1.18.0 | +| CVE Lite command | `npx tsx src/index.ts examples/storybook --json --all` | +| yarn audit command | `yarn npm audit` (Yarn 4.10.3, default direct-deps scope) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/storybook/yarn.lock` from [storybookjs/storybook@cc19ae1](https://github.com/storybookjs/storybook/commit/cc19ae1a2145e8f7cda8dc869f1b90d5346dcedb) | +| Packages parsed (CVE Lite) | 3,008 | +| Unique vulnerable packages (CVE Lite) | 92 | +| Vulnerability entries (yarn npm audit) | 3 | +| Fix command groups (CVE Lite) | 1 | +| Skipped findings with reason (CVE Lite) | 90 | Reproduce CVE Lite locally from the repository root: @@ -180,100 +180,100 @@ All 92 baseline findings remain open at the time of this study. No remediation w Full vulnerable package list from the verified scan on 2026-05-27 (revision `cc19ae1`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| form-data | 2.3.3 | critical | transitive | 2.5.4 | GHSA-fjxv-7rqg-78g4 | -| handlebars | 4.7.8 | critical | transitive | 4.7.9 | GHSA-2qvq-rjwj-gvw9, GHSA-2w6w-674q-4c4q… | -| happy-dom | 17.6.3 | critical | transitive | 20.8.9 | GHSA-37j7-fg3j-429f, GHSA-6q6h-j7hj-3r64… | -| next | 15.5.6 | critical | transitive | 15.5.18 | GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6… | -| simple-git | 3.30.0 | critical | transitive | 3.36.0 | GHSA-hffm-xvc3-vprc, GHSA-jcxm-m3jx-f287… | -| @angular/common | 19.2.15 | high | transitive | 19.2.16 | GHSA-58c5-g7wp-6w37 | -| @angular/compiler | 19.2.15 | high | transitive | 19.2.20 | GHSA-g93w-mfhg-p222, GHSA-jrmj-c5cx-3cw6… | -| @angular/core | 19.2.15 | high | transitive | 19.2.20 | GHSA-g93w-mfhg-p222, GHSA-jrmj-c5cx-3cw6… | -| @babel/plugin-transform-modules-systemjs | 7.28.5 | high | transitive | 7.29.4 | GHSA-fv7c-fp4j-7gwp | -| @remix-run/router | 1.8.0 | high | transitive | 1.23.2 | GHSA-2w69-qvjg-hvjx | -| axios | 1.13.2 | high | transitive | 1.15.2 | GHSA-3p68-rc4w-qgx5, GHSA-3w6x-2g7m-8v23… | -| cross-spawn | 6.0.5 | high | transitive | 6.0.6 | GHSA-3xgq-45jj-v275 | -| fast-uri | 3.0.6 | high | transitive | 3.1.2 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | -| flatted | 3.2.9 | high | transitive | 3.4.2 | GHSA-25h7-pfq9-p65f, GHSA-rf6f-7fwh-wjgh | -| happy-dom | 20.0.11 | high | transitive | 20.8.9 | GHSA-6q6h-j7hj-3r64, GHSA-w4gp-fjgq-3q4g | -| immutable | 5.1.4 | high | transitive | 5.1.5 | GHSA-wf6x-7x77-mvgw | -| js-cookie | 3.0.5 | high | transitive | 3.0.7 | GHSA-qjx8-664m-686j | -| jws | 3.2.2 | high | transitive | 3.2.3 | GHSA-869p-cjfg-cm3x | -| jws | 4.0.0 | high | transitive | 4.0.1 | GHSA-869p-cjfg-cm3x | -| lodash | 4.17.21 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc… | -| lodash-es | 4.17.21 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc… | -| minimatch | 3.1.2 | high | transitive | 3.1.4 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | -| minimatch | 5.1.6 | high | transitive | 5.1.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | -| minimatch | 7.4.6 | high | transitive | 7.4.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | -| minimatch | 9.0.1 | high | transitive | 9.0.7 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | -| minimatch | 9.0.5 | high | transitive | 9.0.7 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | -| node-forge | 1.3.1 | high | transitive | 1.4.0 | GHSA-2328-f5f3-gj25, GHSA-554w-wpv2-vw27… | -| path-to-regexp | 0.1.10 | high | transitive | 0.1.13 | GHSA-37ch-88jc-xwx2, GHSA-rhx6-c78j-4q9w | -| path-to-regexp | 0.1.12 | high | transitive | 0.1.13 | GHSA-37ch-88jc-xwx2 | -| picomatch | 2.3.1 | high | transitive | 2.3.2 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | -| picomatch | 4.0.2 | high | transitive | 4.0.4 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | -| preact | 10.27.2 | high | transitive | 10.27.3 | GHSA-36hm-qxxp-pg3m | -| rollup | 4.34.8 | high | transitive | 4.59.0 | GHSA-mw96-cpmx-2vgc | -| rollup | 4.53.2 | high | transitive | 4.59.0 | GHSA-mw96-cpmx-2vgc | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | -| ssri | 5.3.0 | high | transitive | 6.0.2 | GHSA-vx3p-948g-6vhq | -| tar | 6.2.0 | high | transitive | 7.5.11 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28cx… | -| tar | 7.5.2 | high | transitive | 7.5.11 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28cx… | -| tmp | 0.0.28 | high | transitive | 0.2.6 | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | -| tmp | 0.0.33 | high | transitive | 0.2.6 | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | -| tmp | 0.2.5 | high | transitive | 0.2.6 | GHSA-ph9p-34f9-6g65 | -| undici | 5.27.2 | high | transitive | 6.24.0 | GHSA-2mjp-6q6p-2qxm, GHSA-3787-6prv-h9w3… | -| validator | 13.12.0 | high | transitive | 13.15.22 | GHSA-9965-vmph-33xx, GHSA-vghf-hv5q-vc2g | -| vite | 6.4.1 | high | direct | 6.4.2 | GHSA-4w7w-66w2-5vf9, GHSA-p9ff-h696-f583 | -| vite | 7.2.2 | high | direct | 7.3.2 | GHSA-4w7w-66w2-5vf9, GHSA-p9ff-h696-f583… | -| @octokit/plugin-paginate-rest | 2.21.3 | medium | transitive | 9.2.2 | GHSA-h5c3-5r3r-rr8q | -| @octokit/request | 5.6.3 | medium | transitive | 8.4.1 | GHSA-rmvr-2pp2-xj38 | -| @octokit/request | 6.2.8 | medium | transitive | 8.4.1 | GHSA-rmvr-2pp2-xj38 | -| @octokit/request-error | 2.1.0 | medium | transitive | 5.1.1 | GHSA-xx4v-prfh-6cgc | -| @octokit/request-error | 3.0.3 | medium | transitive | 5.1.1 | GHSA-xx4v-prfh-6cgc | -| @tanstack/start-server-core | 1.167.9 | medium | transitive | 1.167.30 | GHSA-9m65-766c-r333 | -| ajv | 6.12.6 | medium | transitive | 6.14.0 | GHSA-2g4f-4pwh-qvx6 | -| ajv | 8.17.1 | medium | transitive | 8.18.0 | GHSA-2g4f-4pwh-qvx6 | -| bn.js | 4.12.2 | medium | transitive | 4.12.3 | GHSA-378v-28hj-76wf | -| bn.js | 5.2.2 | medium | transitive | 5.2.3 | GHSA-378v-28hj-76wf | -| brace-expansion | 1.1.11 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v, GHSA-v6h2-p8h4-qcjw | -| brace-expansion | 2.0.1 | medium | transitive | 2.0.3 | GHSA-f886-m6hf-6m8v, GHSA-v6h2-p8h4-qcjw | -| brace-expansion | 5.0.4 | medium | transitive | 5.0.6 | GHSA-f886-m6hf-6m8v, GHSA-jxxr-4gwj-5jf2 | -| follow-redirects | 1.15.6 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | -| h3 | 2.0.1-rc.16 | medium | transitive | ⚠ no fix | GHSA-2j6q-whv2-gh6w, GHSA-4hxc-9384-m385… | -| ip-address | 10.1.0 | medium | transitive | 10.1.1 | GHSA-v2v4-37r5-5v8g | -| js-yaml | 3.14.1 | medium | transitive | 3.14.2 | GHSA-mh29-5h37-fv8m | -| js-yaml | 4.1.0 | medium | transitive | 4.1.1 | GHSA-mh29-5h37-fv8m | -| mdast-util-to-hast | 13.2.0 | medium | transitive | 13.2.1 | GHSA-4fh9-h7wg-q85m | -| nanoid | 4.0.2 | medium | transitive | 5.0.9 | GHSA-mwcw-c2x4-8c55 | -| postcss | 8.4.31 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | -| postcss | 8.5.2 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | -| prismjs | 1.27.0 | medium | transitive | 1.30.0 | GHSA-x7hr-w5r2-h6wg | -| qs | 6.13.0 | medium | transitive | 6.15.2 | GHSA-6rw7-vpxm-498p, GHSA-q8mj-m7cp-5q26… | -| qs | 6.14.0 | medium | transitive | 6.15.2 | GHSA-6rw7-vpxm-498p, GHSA-q8mj-m7cp-5q26… | -| qs | 6.5.3 | medium | transitive | 6.14.1 | GHSA-6rw7-vpxm-498p | -| react-router | 6.15.0 | medium | transitive | 6.30.2 | GHSA-9jcx-v3wj-wh4m | -| request | 2.88.2 | medium | transitive | 3.0.0 | GHSA-p8p7-x288-28g6 | -| smol-toml | 1.5.2 | medium | transitive | 1.6.1 | GHSA-v3rj-xjv7-4jmq | -| svelte | 5.43.11 | medium | transitive | 5.55.7 | GHSA-crpf-4hrx-3jrp, GHSA-f7gr-6p89-r883… | -| tough-cookie | 2.5.0 | medium | transitive | 4.1.3 | GHSA-72xf-g2v4-qvf3 | -| uuid | 11.1.0 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | -| uuid | 3.4.0 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | -| uuid | 8.3.2 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | -| uuid | 9.0.1 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | -| webpack-dev-server | 5.2.2 | medium | transitive | 5.2.4 | GHSA-79cf-xcqc-c78w | -| ws | 8.19.0 | medium | transitive | 8.20.1 | GHSA-58qx-3vcg-4xpx | -| yaml | 1.10.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | -| yaml | 2.8.2 | medium | transitive | 2.8.3 | GHSA-48c2-rrv3-qjmp | -| @tootallnate/once | 1.1.2 | low | transitive | 2.0.1 | GHSA-vpq2-c234-7xj6 | -| @tootallnate/once | 2.0.0 | low | transitive | 2.0.1 | GHSA-vpq2-c234-7xj6 | -| cookie | 0.6.0 | low | transitive | 0.7.0 | GHSA-pxg6-pf52-xh8x | -| diff | 8.0.2 | low | transitive | 8.0.3 | GHSA-73rr-hh4g-fpgx | -| elliptic | 6.6.1 | low | transitive | ⚠ no fix | GHSA-848j-6mx2-7j84 | -| on-headers | 1.0.2 | low | transitive | 1.1.0 | GHSA-76c9-3jph-rj3q | -| webpack | 5.103.0 | low | transitive | 5.104.1 | GHSA-38r7-794h-5758, GHSA-8fgc-7cc6-rx7x | -| webpack | 5.98.0 | low | transitive | 5.104.1 | GHSA-38r7-794h-5758, GHSA-8fgc-7cc6-rx7x | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ---------------------------------------- | ----------- | -------- | ------------ | -------- | ----------------------------------------- | +| form-data | 2.3.3 | critical | transitive | 2.5.4 | GHSA-fjxv-7rqg-78g4 | +| handlebars | 4.7.8 | critical | transitive | 4.7.9 | GHSA-2qvq-rjwj-gvw9, GHSA-2w6w-674q-4c4q… | +| happy-dom | 17.6.3 | critical | transitive | 20.8.9 | GHSA-37j7-fg3j-429f, GHSA-6q6h-j7hj-3r64… | +| next | 15.5.6 | critical | transitive | 15.5.18 | GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6… | +| simple-git | 3.30.0 | critical | transitive | 3.36.0 | GHSA-hffm-xvc3-vprc, GHSA-jcxm-m3jx-f287… | +| @angular/common | 19.2.15 | high | transitive | 19.2.16 | GHSA-58c5-g7wp-6w37 | +| @angular/compiler | 19.2.15 | high | transitive | 19.2.20 | GHSA-g93w-mfhg-p222, GHSA-jrmj-c5cx-3cw6… | +| @angular/core | 19.2.15 | high | transitive | 19.2.20 | GHSA-g93w-mfhg-p222, GHSA-jrmj-c5cx-3cw6… | +| @babel/plugin-transform-modules-systemjs | 7.28.5 | high | transitive | 7.29.4 | GHSA-fv7c-fp4j-7gwp | +| @remix-run/router | 1.8.0 | high | transitive | 1.23.2 | GHSA-2w69-qvjg-hvjx | +| axios | 1.13.2 | high | transitive | 1.15.2 | GHSA-3p68-rc4w-qgx5, GHSA-3w6x-2g7m-8v23… | +| cross-spawn | 6.0.5 | high | transitive | 6.0.6 | GHSA-3xgq-45jj-v275 | +| fast-uri | 3.0.6 | high | transitive | 3.1.2 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | +| flatted | 3.2.9 | high | transitive | 3.4.2 | GHSA-25h7-pfq9-p65f, GHSA-rf6f-7fwh-wjgh | +| happy-dom | 20.0.11 | high | transitive | 20.8.9 | GHSA-6q6h-j7hj-3r64, GHSA-w4gp-fjgq-3q4g | +| immutable | 5.1.4 | high | transitive | 5.1.5 | GHSA-wf6x-7x77-mvgw | +| js-cookie | 3.0.5 | high | transitive | 3.0.7 | GHSA-qjx8-664m-686j | +| jws | 3.2.2 | high | transitive | 3.2.3 | GHSA-869p-cjfg-cm3x | +| jws | 4.0.0 | high | transitive | 4.0.1 | GHSA-869p-cjfg-cm3x | +| lodash | 4.17.21 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc… | +| lodash-es | 4.17.21 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc… | +| minimatch | 3.1.2 | high | transitive | 3.1.4 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | +| minimatch | 5.1.6 | high | transitive | 5.1.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | +| minimatch | 7.4.6 | high | transitive | 7.4.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | +| minimatch | 9.0.1 | high | transitive | 9.0.7 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | +| minimatch | 9.0.5 | high | transitive | 9.0.7 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26… | +| node-forge | 1.3.1 | high | transitive | 1.4.0 | GHSA-2328-f5f3-gj25, GHSA-554w-wpv2-vw27… | +| path-to-regexp | 0.1.10 | high | transitive | 0.1.13 | GHSA-37ch-88jc-xwx2, GHSA-rhx6-c78j-4q9w | +| path-to-regexp | 0.1.12 | high | transitive | 0.1.13 | GHSA-37ch-88jc-xwx2 | +| picomatch | 2.3.1 | high | transitive | 2.3.2 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | +| picomatch | 4.0.2 | high | transitive | 4.0.4 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | +| preact | 10.27.2 | high | transitive | 10.27.3 | GHSA-36hm-qxxp-pg3m | +| rollup | 4.34.8 | high | transitive | 4.59.0 | GHSA-mw96-cpmx-2vgc | +| rollup | 4.53.2 | high | transitive | 4.59.0 | GHSA-mw96-cpmx-2vgc | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | +| ssri | 5.3.0 | high | transitive | 6.0.2 | GHSA-vx3p-948g-6vhq | +| tar | 6.2.0 | high | transitive | 7.5.11 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28cx… | +| tar | 7.5.2 | high | transitive | 7.5.11 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28cx… | +| tmp | 0.0.28 | high | transitive | 0.2.6 | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | +| tmp | 0.0.33 | high | transitive | 0.2.6 | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | +| tmp | 0.2.5 | high | transitive | 0.2.6 | GHSA-ph9p-34f9-6g65 | +| undici | 5.27.2 | high | transitive | 6.24.0 | GHSA-2mjp-6q6p-2qxm, GHSA-3787-6prv-h9w3… | +| validator | 13.12.0 | high | transitive | 13.15.22 | GHSA-9965-vmph-33xx, GHSA-vghf-hv5q-vc2g | +| vite | 6.4.1 | high | direct | 6.4.2 | GHSA-4w7w-66w2-5vf9, GHSA-p9ff-h696-f583 | +| vite | 7.2.2 | high | direct | 7.3.2 | GHSA-4w7w-66w2-5vf9, GHSA-p9ff-h696-f583… | +| @octokit/plugin-paginate-rest | 2.21.3 | medium | transitive | 9.2.2 | GHSA-h5c3-5r3r-rr8q | +| @octokit/request | 5.6.3 | medium | transitive | 8.4.1 | GHSA-rmvr-2pp2-xj38 | +| @octokit/request | 6.2.8 | medium | transitive | 8.4.1 | GHSA-rmvr-2pp2-xj38 | +| @octokit/request-error | 2.1.0 | medium | transitive | 5.1.1 | GHSA-xx4v-prfh-6cgc | +| @octokit/request-error | 3.0.3 | medium | transitive | 5.1.1 | GHSA-xx4v-prfh-6cgc | +| @tanstack/start-server-core | 1.167.9 | medium | transitive | 1.167.30 | GHSA-9m65-766c-r333 | +| ajv | 6.12.6 | medium | transitive | 6.14.0 | GHSA-2g4f-4pwh-qvx6 | +| ajv | 8.17.1 | medium | transitive | 8.18.0 | GHSA-2g4f-4pwh-qvx6 | +| bn.js | 4.12.2 | medium | transitive | 4.12.3 | GHSA-378v-28hj-76wf | +| bn.js | 5.2.2 | medium | transitive | 5.2.3 | GHSA-378v-28hj-76wf | +| brace-expansion | 1.1.11 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v, GHSA-v6h2-p8h4-qcjw | +| brace-expansion | 2.0.1 | medium | transitive | 2.0.3 | GHSA-f886-m6hf-6m8v, GHSA-v6h2-p8h4-qcjw | +| brace-expansion | 5.0.4 | medium | transitive | 5.0.6 | GHSA-f886-m6hf-6m8v, GHSA-jxxr-4gwj-5jf2 | +| follow-redirects | 1.15.6 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | +| h3 | 2.0.1-rc.16 | medium | transitive | ⚠ no fix | GHSA-2j6q-whv2-gh6w, GHSA-4hxc-9384-m385… | +| ip-address | 10.1.0 | medium | transitive | 10.1.1 | GHSA-v2v4-37r5-5v8g | +| js-yaml | 3.14.1 | medium | transitive | 3.14.2 | GHSA-mh29-5h37-fv8m | +| js-yaml | 4.1.0 | medium | transitive | 4.1.1 | GHSA-mh29-5h37-fv8m | +| mdast-util-to-hast | 13.2.0 | medium | transitive | 13.2.1 | GHSA-4fh9-h7wg-q85m | +| nanoid | 4.0.2 | medium | transitive | 5.0.9 | GHSA-mwcw-c2x4-8c55 | +| postcss | 8.4.31 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | +| postcss | 8.5.2 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | +| prismjs | 1.27.0 | medium | transitive | 1.30.0 | GHSA-x7hr-w5r2-h6wg | +| qs | 6.13.0 | medium | transitive | 6.15.2 | GHSA-6rw7-vpxm-498p, GHSA-q8mj-m7cp-5q26… | +| qs | 6.14.0 | medium | transitive | 6.15.2 | GHSA-6rw7-vpxm-498p, GHSA-q8mj-m7cp-5q26… | +| qs | 6.5.3 | medium | transitive | 6.14.1 | GHSA-6rw7-vpxm-498p | +| react-router | 6.15.0 | medium | transitive | 6.30.2 | GHSA-9jcx-v3wj-wh4m | +| request | 2.88.2 | medium | transitive | 3.0.0 | GHSA-p8p7-x288-28g6 | +| smol-toml | 1.5.2 | medium | transitive | 1.6.1 | GHSA-v3rj-xjv7-4jmq | +| svelte | 5.43.11 | medium | transitive | 5.55.7 | GHSA-crpf-4hrx-3jrp, GHSA-f7gr-6p89-r883… | +| tough-cookie | 2.5.0 | medium | transitive | 4.1.3 | GHSA-72xf-g2v4-qvf3 | +| uuid | 11.1.0 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | +| uuid | 3.4.0 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | +| uuid | 8.3.2 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | +| uuid | 9.0.1 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | +| webpack-dev-server | 5.2.2 | medium | transitive | 5.2.4 | GHSA-79cf-xcqc-c78w | +| ws | 8.19.0 | medium | transitive | 8.20.1 | GHSA-58qx-3vcg-4xpx | +| yaml | 1.10.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | +| yaml | 2.8.2 | medium | transitive | 2.8.3 | GHSA-48c2-rrv3-qjmp | +| @tootallnate/once | 1.1.2 | low | transitive | 2.0.1 | GHSA-vpq2-c234-7xj6 | +| @tootallnate/once | 2.0.0 | low | transitive | 2.0.1 | GHSA-vpq2-c234-7xj6 | +| cookie | 0.6.0 | low | transitive | 0.7.0 | GHSA-pxg6-pf52-xh8x | +| diff | 8.0.2 | low | transitive | 8.0.3 | GHSA-73rr-hh4g-fpgx | +| elliptic | 6.6.1 | low | transitive | ⚠ no fix | GHSA-848j-6mx2-7j84 | +| on-headers | 1.0.2 | low | transitive | 1.1.0 | GHSA-76c9-3jph-rj3q | +| webpack | 5.103.0 | low | transitive | 5.104.1 | GHSA-38r7-794h-5758, GHSA-8fgc-7cc6-rx7x | +| webpack | 5.98.0 | low | transitive | 5.104.1 | GHSA-38r7-794h-5758, GHSA-8fgc-7cc6-rx7x | --- diff --git a/website/docs/case-studies/strapi.md b/website/docs/case-studies/strapi.md index 7d562b12..d625e989 100644 --- a/website/docs/case-studies/strapi.md +++ b/website/docs/case-studies/strapi.md @@ -41,12 +41,12 @@ This is the same critical package class flagged in the [Storybook](./storybook.m **Direct vs transitive remediation split:** -| Category | Examples | CVE Lite action | -|---|---|---| -| Direct, fixable now | `lodash@4.17.23`, `qs@6.14.2` | `yarn add lodash@4.18.0`, `yarn add qs@6.15.2` | -| Transitive, within-range refresh | `handlebars`, `axios`, `minimatch@5.1.0`, `cross-spawn`, `tmp@0.2.1` | `yarn upgrade ` | -| Transitive, parent upgrade needed | `minimatch@9.0.3` via `@nx/js`, `minimatch@9.0.5` via `syncpack`, `file-type` via `@swc/cli` | Partial parent commands + rescan | -| No fix available | `html-minifier@3.5.21` | `⚠ no fix` — wait for upstream | +| Category | Examples | CVE Lite action | +| --------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| Direct, fixable now | `lodash@4.17.23`, `qs@6.14.2` | `yarn add lodash@4.18.0`, `yarn add qs@6.15.2` | +| Transitive, within-range refresh | `handlebars`, `axios`, `minimatch@5.1.0`, `cross-spawn`, `tmp@0.2.1` | `yarn upgrade ` | +| Transitive, parent upgrade needed | `minimatch@9.0.3` via `@nx/js`, `minimatch@9.0.5` via `syncpack`, `file-type` via `@swc/cli` | Partial parent commands + rescan | +| No fix available | `html-minifier@3.5.21` | `⚠ no fix` — wait for upstream | **`lodash@4.17.23` — high, direct.** CVE Lite generates a validated direct upgrade — the clearest "edit root manifest and rescan" outcome on this snapshot: @@ -62,19 +62,19 @@ yarn add lodash@4.18.0 Both tools were attempted against the same `yarn.lock` on the same machine on 2026-06-09. -| Metric | yarn npm audit (4.12.0) | CVE Lite CLI v1.20.0 | -|---|---:|---:| -| Total reported findings | not runnable¹ | 29 | -| Critical | — | 1 | -| High | — | 12 | -| Moderate / Medium | — | 13 | -| Low | — | 3 | -| Direct vs transitive breakdown | ✗ | ✓ (2 / 15 / 12 unknown) | -| Full lockfile package parse | ✗ (requires install) | ✓ (2,887 packages) | -| Deduplicated package view | ✗ | ✓ | -| Packages with no known fix flagged | ✗ | ✓ (`html-minifier`) | -| Specific copy-and-run commands | ✗ | ✓ (6 groups) | -| Skipped findings with reason | ✗ | ✓ (17 entries) | +| Metric | yarn npm audit (4.12.0) | CVE Lite CLI v1.20.0 | +| ---------------------------------- | ----------------------: | ----------------------: | +| Total reported findings | not runnable¹ | 29 | +| Critical | — | 1 | +| High | — | 12 | +| Moderate / Medium | — | 13 | +| Low | — | 3 | +| Direct vs transitive breakdown | ✗ | ✓ (2 / 15 / 12 unknown) | +| Full lockfile package parse | ✗ (requires install) | ✓ (2,887 packages) | +| Deduplicated package view | ✗ | ✓ | +| Packages with no known fix flagged | ✗ | ✓ (`html-minifier`) | +| Specific copy-and-run commands | ✗ | ✓ (6 groups) | +| Skipped findings with reason | ✗ | ✓ (17 entries) | ¹ **`yarn npm audit` fails on this fixture** with `Internal Error: vitest@catalog:: default catalog not found or empty`. Strapi's root `package.json` uses Yarn Berry **catalog** and **workspace** protocols that require the full monorepo context — workspace packages, `.yarnrc.yml`, and `node_modules` — not present in this lockfile-only snapshot. This is the same class of limitation documented in the [Storybook](./storybook.md) case study. @@ -88,9 +88,9 @@ On a committed lockfile snapshot, CVE Lite parses all **2,887 resolved packages* No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Unknown | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 29 | 1 | 12 | 13 | 3 | 2 | 15 | 12 | 6 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Unknown | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | ------: | -------------: | +| Baseline (verified) | 29 | 1 | 12 | 13 | 3 | 2 | 15 | 12 | 6 | The first-pass plan covers **12 of 29** findings across six command groups. The remaining **17** appear in the skipped section — unknown-relationship packages where Yarn Berry path reconstruction is limited, transitive packages awaiting parent releases (`@nx/js`, `syncpack`, `@commitlint/prompt-cli`), or packages with no fix (`html-minifier`). @@ -170,20 +170,20 @@ The example lockfile reflects Strapi at revision `e666ee26ae8e8c1758a048d6385afe Every number in this case study comes from a live scan of the committed fixture at `examples/strapi/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-06-09 | -| CLI version | v1.20.0 | -| CVE Lite command | `node dist/index.js examples/strapi --verbose --all --json` | -| yarn audit command | `yarn npm audit` (Yarn 4.12.0 — fails: catalog protocol requires full install) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/strapi/yarn.lock` from [strapi/strapi@e666ee2](https://github.com/strapi/strapi/commit/e666ee26ae8e8c1758a048d6385afe1a62790a84) | -| Packages parsed (CVE Lite) | 2,887 | -| Unique vulnerable packages (CVE Lite) | 29 | -| OSV advisory matches (CVE Lite) | 61 | -| Fix command groups (CVE Lite) | 6 | -| First-pass coverage (CVE Lite) | 12 / 29 findings | -| Skipped findings with reason (CVE Lite) | 17 | +| Field | Value | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| Scan date | 2026-06-09 | +| CLI version | v1.20.0 | +| CVE Lite command | `node dist/index.js examples/strapi --verbose --all --json` | +| yarn audit command | `yarn npm audit` (Yarn 4.12.0 — fails: catalog protocol requires full install) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/strapi/yarn.lock` from [strapi/strapi@e666ee2](https://github.com/strapi/strapi/commit/e666ee26ae8e8c1758a048d6385afe1a62790a84) | +| Packages parsed (CVE Lite) | 2,887 | +| Unique vulnerable packages (CVE Lite) | 29 | +| OSV advisory matches (CVE Lite) | 61 | +| Fix command groups (CVE Lite) | 6 | +| First-pass coverage (CVE Lite) | 12 / 29 findings | +| Skipped findings with reason (CVE Lite) | 17 | Reproduce CVE Lite locally from the repository root: @@ -209,37 +209,37 @@ All 29 baseline findings remain open at the time of this study. No remediation w Full vulnerable package list from the verified scan on 2026-06-09 (revision `e666ee2`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| handlebars | 4.7.8 | critical | transitive | 4.7.9 | GHSA-2qvq-rjwj-gvw9, GHSA-2w6w-674q-4c4q | -| axios | 1.13.6 | high | transitive | 1.16.0 | GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw | -| cross-spawn | 6.0.5 | high | transitive | 6.0.6 | GHSA-3xgq-45jj-v275 | -| html-minifier | 3.5.21 | high | transitive | ⚠ no fix | GHSA-pfq8-rq6v-vf5m | -| lodash | 4.17.23 | high | direct | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | -| minimatch | 9.0.3 | high | transitive | 9.0.7 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 9.0.5 | high | transitive | 9.0.7 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 5.1.0 | high | transitive | 5.1.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| path-to-regexp | 8.3.0 | high | unknown | 8.4.0 ⊘ | GHSA-27v5-c462-wpq7, GHSA-j3q9-mxjg-w52f | -| tmp | 0.0.33 | high | transitive | 0.2.6 ⊘ | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | -| tmp | 0.2.5 | high | unknown | 0.2.6 ⊘ | GHSA-ph9p-34f9-6g65 | -| tmp | 0.2.1 | high | transitive | 0.2.6 | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | -| undici | 5.29.0 | high | unknown | 6.24.0 ⊘ | GHSA-2mjp-6q6p-2qxm, GHSA-4992-7rv2-5pvq | -| @apollo/server | 4.13.0 | medium | unknown | 5.5.0 ⊘ | GHSA-9q82-xgwf-vj6h | -| brace-expansion | 5.0.5 | medium | transitive | 5.0.6 | GHSA-jxxr-4gwj-5jf2 | -| ejs | 3.1.9 | medium | transitive | 3.1.10 | GHSA-ghr5-ch3p-vcr6 | -| esbuild | 0.21.5 | medium | unknown | 0.25.0 ⊘ | GHSA-67mh-4wv8-2f99 | -| file-type | 20.5.0 | medium | transitive | 21.3.1 ⊘ | GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v | -| qs | 6.14.2 | medium | direct | 6.15.2 | GHSA-q8mj-m7cp-5q26 | -| qs | 6.15.1 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | -| react-router | 6.30.3 | medium | unknown | 6.30.4 ⊘ | GHSA-2j2x-hqr9-3h42 | -| tough-cookie | 4.0.0 | medium | transitive | 4.1.4 | GHSA-72xf-g2v4-qvf3 | -| uuid | 8.3.2 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | -| uuid | 9.0.1 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | -| vite | 5.4.21 | medium | unknown | 6.4.2 ⊘ | GHSA-4w7w-66w2-5vf9 | -| yaml | 2.5.1 | medium | transitive | 2.8.2 | GHSA-48c2-rrv3-qjmp | -| @ai-sdk/provider-utils | 3.0.20 | low | unknown | ⚠ no fix | GHSA-866g-f22w-33x8 | -| @ai-sdk/provider-utils | 3.0.9 | low | unknown | ⚠ no fix | GHSA-866g-f22w-33x8 | -| elliptic | 6.6.1 | low | unknown | ⚠ no fix | GHSA-848j-6mx2-7j84 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ---------------------- | ------- | -------- | ------------ | -------- | ---------------------------------------- | +| handlebars | 4.7.8 | critical | transitive | 4.7.9 | GHSA-2qvq-rjwj-gvw9, GHSA-2w6w-674q-4c4q | +| axios | 1.13.6 | high | transitive | 1.16.0 | GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw | +| cross-spawn | 6.0.5 | high | transitive | 6.0.6 | GHSA-3xgq-45jj-v275 | +| html-minifier | 3.5.21 | high | transitive | ⚠ no fix | GHSA-pfq8-rq6v-vf5m | +| lodash | 4.17.23 | high | direct | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | +| minimatch | 9.0.3 | high | transitive | 9.0.7 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 9.0.5 | high | transitive | 9.0.7 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 5.1.0 | high | transitive | 5.1.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| path-to-regexp | 8.3.0 | high | unknown | 8.4.0 ⊘ | GHSA-27v5-c462-wpq7, GHSA-j3q9-mxjg-w52f | +| tmp | 0.0.33 | high | transitive | 0.2.6 ⊘ | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | +| tmp | 0.2.5 | high | unknown | 0.2.6 ⊘ | GHSA-ph9p-34f9-6g65 | +| tmp | 0.2.1 | high | transitive | 0.2.6 | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | +| undici | 5.29.0 | high | unknown | 6.24.0 ⊘ | GHSA-2mjp-6q6p-2qxm, GHSA-4992-7rv2-5pvq | +| @apollo/server | 4.13.0 | medium | unknown | 5.5.0 ⊘ | GHSA-9q82-xgwf-vj6h | +| brace-expansion | 5.0.5 | medium | transitive | 5.0.6 | GHSA-jxxr-4gwj-5jf2 | +| ejs | 3.1.9 | medium | transitive | 3.1.10 | GHSA-ghr5-ch3p-vcr6 | +| esbuild | 0.21.5 | medium | unknown | 0.25.0 ⊘ | GHSA-67mh-4wv8-2f99 | +| file-type | 20.5.0 | medium | transitive | 21.3.1 ⊘ | GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v | +| qs | 6.14.2 | medium | direct | 6.15.2 | GHSA-q8mj-m7cp-5q26 | +| qs | 6.15.1 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | +| react-router | 6.30.3 | medium | unknown | 6.30.4 ⊘ | GHSA-2j2x-hqr9-3h42 | +| tough-cookie | 4.0.0 | medium | transitive | 4.1.4 | GHSA-72xf-g2v4-qvf3 | +| uuid | 8.3.2 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | +| uuid | 9.0.1 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | +| vite | 5.4.21 | medium | unknown | 6.4.2 ⊘ | GHSA-4w7w-66w2-5vf9 | +| yaml | 2.5.1 | medium | transitive | 2.8.2 | GHSA-48c2-rrv3-qjmp | +| @ai-sdk/provider-utils | 3.0.20 | low | unknown | ⚠ no fix | GHSA-866g-f22w-33x8 | +| @ai-sdk/provider-utils | 3.0.9 | low | unknown | ⚠ no fix | GHSA-866g-f22w-33x8 | +| elliptic | 6.6.1 | low | unknown | ⚠ no fix | GHSA-848j-6mx2-7j84 | --- diff --git a/website/docs/case-studies/turborepo.md b/website/docs/case-studies/turborepo.md index a6cb5841..3e23f614 100644 --- a/website/docs/case-studies/turborepo.md +++ b/website/docs/case-studies/turborepo.md @@ -47,19 +47,19 @@ Other high-severity findings show the docs-and-examples layer: Both tools were run against the same `pnpm-lock.yaml` on the same machine on 2026-05-27. -| Metric | pnpm audit (10.28.0) | CVE Lite CLI v1.18.0 | -|---|---:|---:| -| Total reported findings | 28 | 13 | -| Critical | 0 | 1 | -| High | 13 | 5 | -| Moderate / Medium | 11 | 7 | -| Low | 4 | 0 | -| Direct vs transitive breakdown | ✗ | ✓ (0 / 13) | -| Deduplicated package view | ✗ | ✓ | -| Packages with no known fix flagged | ✗ | ✓ (1 package) | -| Validated fix targets | partial | ✓ | -| Specific copy-and-run commands | ✗ | ✗ (0 groups) | -| Skipped findings with reason | ✗ | ✓ (13 entries) | +| Metric | pnpm audit (10.28.0) | CVE Lite CLI v1.18.0 | +| ---------------------------------- | -------------------: | -------------------: | +| Total reported findings | 28 | 13 | +| Critical | 0 | 1 | +| High | 13 | 5 | +| Moderate / Medium | 11 | 7 | +| Low | 4 | 0 | +| Direct vs transitive breakdown | ✗ | ✓ (0 / 13) | +| Deduplicated package view | ✗ | ✓ | +| Packages with no known fix flagged | ✗ | ✓ (1 package) | +| Validated fix targets | partial | ✓ | +| Specific copy-and-run commands | ✗ | ✗ (0 groups) | +| Skipped findings with reason | ✗ | ✓ (13 entries) | **Why the totals differ — and why that is not a coverage gap:** @@ -81,9 +81,9 @@ On this lockfile-only snapshot, CVE Lite generates **zero copy-and-run command g No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 13 | 1 | 5 | 7 | 0 | 0 | 13 | 0 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 13 | 1 | 5 | 7 | 0 | 0 | 13 | 0 | Zero command groups is itself a meaningful result. On a professionally maintained build-tool monorepo, the first scan answer may be "nothing to run locally yet — triage upstream and docs-app dependencies first." @@ -127,19 +127,19 @@ The example lockfile reflects Turborepo at revision `c85d4104bdc18df051334210d29 Every number in this case study comes from a live scan of the committed fixture at `examples/turborepo/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-05-27 | -| CLI version | v1.18.0 | -| CVE Lite command | `npx tsx src/index.ts examples/turborepo --json --all` | -| pnpm audit command | `pnpm audit` (pnpm 10.28.0) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/turborepo/pnpm-lock.yaml` from [vercel/turborepo@c85d410](https://github.com/vercel/turborepo/commit/c85d4104bdc18df051334210d29c49353c46facf) | -| Packages parsed (CVE Lite) | 1,776 | -| Unique vulnerable packages (CVE Lite) | 13 | -| Vulnerability entries (pnpm audit) | 28 | -| Fix command groups (CVE Lite) | 0 | -| Skipped findings with reason (CVE Lite) | 13 | +| Field | Value | +| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-05-27 | +| CLI version | v1.18.0 | +| CVE Lite command | `npx tsx src/index.ts examples/turborepo --json --all` | +| pnpm audit command | `pnpm audit` (pnpm 10.28.0) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/turborepo/pnpm-lock.yaml` from [vercel/turborepo@c85d410](https://github.com/vercel/turborepo/commit/c85d4104bdc18df051334210d29c49353c46facf) | +| Packages parsed (CVE Lite) | 1,776 | +| Unique vulnerable packages (CVE Lite) | 13 | +| Vulnerability entries (pnpm audit) | 28 | +| Fix command groups (CVE Lite) | 0 | +| Skipped findings with reason (CVE Lite) | 13 | Reproduce CVE Lite locally from the repository root: @@ -174,21 +174,21 @@ All 13 baseline findings remain open at the time of this study. No remediation w Full vulnerable package list from the verified scan on 2026-05-27 (revision `c85d410`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| sandbox | 3.0.0-beta.16 | critical | transitive | ⚠ no fix | GHSA-fm4j-4xhm-xpwx, GHSA-gc25-3vc5-2jf9 | -| basic-ftp | 5.2.2 | high | transitive | 5.3.1 | GHSA-rp42-5vxx-qpwr, GHSA-rpmf-866q-6p89 | -| fast-uri | 3.1.0 | high | transitive | 3.1.2 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | -| fast-xml-builder | 1.1.4 | high | transitive | 1.1.7 | GHSA-5wm8-gmm8-39j9 | -| next | 16.2.3 | high | transitive | 16.2.6 | GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6… | -| tmp | 0.2.5 | high | transitive | 0.2.6 | GHSA-ph9p-34f9-6g65 | -| brace-expansion | 5.0.5 | medium | transitive | 5.0.6 | GHSA-jxxr-4gwj-5jf2 | -| fast-xml-parser | 5.5.11 | medium | transitive | 5.7.0 | GHSA-gh4j-gqv2-49f6 | -| ip-address | 10.1.0 | medium | transitive | 10.1.1 | GHSA-v2v4-37r5-5v8g | -| postcss | 8.4.31 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | -| postcss | 8.5.6 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | -| qs | 6.15.1 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | -| turbo | 2.8.3 | medium | transitive | 2.9.14 | GHSA-3qcw-2rhx-2726, GHSA-hcf7-66rw-9f5r | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ---------------- | ------------- | -------- | ------------ | -------- | ----------------------------------------- | +| sandbox | 3.0.0-beta.16 | critical | transitive | ⚠ no fix | GHSA-fm4j-4xhm-xpwx, GHSA-gc25-3vc5-2jf9 | +| basic-ftp | 5.2.2 | high | transitive | 5.3.1 | GHSA-rp42-5vxx-qpwr, GHSA-rpmf-866q-6p89 | +| fast-uri | 3.1.0 | high | transitive | 3.1.2 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | +| fast-xml-builder | 1.1.4 | high | transitive | 1.1.7 | GHSA-5wm8-gmm8-39j9 | +| next | 16.2.3 | high | transitive | 16.2.6 | GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6… | +| tmp | 0.2.5 | high | transitive | 0.2.6 | GHSA-ph9p-34f9-6g65 | +| brace-expansion | 5.0.5 | medium | transitive | 5.0.6 | GHSA-jxxr-4gwj-5jf2 | +| fast-xml-parser | 5.5.11 | medium | transitive | 5.7.0 | GHSA-gh4j-gqv2-49f6 | +| ip-address | 10.1.0 | medium | transitive | 10.1.1 | GHSA-v2v4-37r5-5v8g | +| postcss | 8.4.31 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | +| postcss | 8.5.6 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | +| qs | 6.15.1 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | +| turbo | 2.8.3 | medium | transitive | 2.9.14 | GHSA-3qcw-2rhx-2726, GHSA-hcf7-66rw-9f5r | --- diff --git a/website/docs/case-studies/twenty.md b/website/docs/case-studies/twenty.md index a2a7ae95..fafe514c 100644 --- a/website/docs/case-studies/twenty.md +++ b/website/docs/case-studies/twenty.md @@ -47,18 +47,18 @@ The six critical packages cluster around **test environments and legacy HTTP hel Both tools were attempted against the same `yarn.lock` on the same machine on 2026-06-09. -| Metric | yarn npm audit (4.13.0) | CVE Lite CLI v1.20.0 | -|---|---:|---:| -| Total reported findings | 0 (no audit suggestions) | 105 | -| Critical | — | 6 | -| High | — | 40 | -| Moderate / Medium | — | 54 | -| Low | — | 5 | -| Direct vs transitive breakdown | ✗ | ✓ (0 / 28 / 77 unknown) | -| Full lockfile package parse | ✗ (requires install) | ✓ (5,451 packages) | -| Deduplicated package view | ✗ | ✓ | -| Specific copy-and-run commands | ✗ | ✓ (4 groups) | -| Skipped findings with reason | ✗ | ✓ (81 entries) | +| Metric | yarn npm audit (4.13.0) | CVE Lite CLI v1.20.0 | +| ------------------------------ | -----------------------: | ----------------------: | +| Total reported findings | 0 (no audit suggestions) | 105 | +| Critical | — | 6 | +| High | — | 40 | +| Moderate / Medium | — | 54 | +| Low | — | 5 | +| Direct vs transitive breakdown | ✗ | ✓ (0 / 28 / 77 unknown) | +| Full lockfile package parse | ✗ (requires install) | ✓ (5,451 packages) | +| Deduplicated package view | ✗ | ✓ | +| Specific copy-and-run commands | ✗ | ✓ (4 groups) | +| Skipped findings with reason | ✗ | ✓ (81 entries) | **Why `yarn npm audit` reports nothing on this fixture:** @@ -76,9 +76,9 @@ A flat audit row count (when audit works at all) multiplies advisory × path ent No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Unknown | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 105 | 6 | 40 | 54 | 5 | 0 | 28 | 77 | 4 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Unknown | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | ------: | -------------: | +| Baseline (verified) | 105 | 6 | 40 | 54 | 5 | 0 | 28 | 77 | 4 | The first-pass plan covers **24 of 105** findings across four command groups. The remaining **81** appear in the skipped section — overwhelmingly unknown-relationship packages where Yarn Berry path reconstruction is limited, or transitive packages awaiting Nx/plugin parent releases. @@ -154,20 +154,20 @@ The example lockfile reflects Twenty at revision `fc90b4ba8bb0a5d7c12c846fe9b230 Every number in this case study comes from a live scan of the committed fixture at `examples/twenty/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-06-09 | -| CLI version | v1.20.0 | -| CVE Lite command | `node dist/index.js examples/twenty --verbose --all --json` | -| yarn audit command | `yarn npm audit` / `yarn npm audit -A` (Yarn 4.13.0 — no audit suggestions on lockfile-only snapshot) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/twenty/yarn.lock` from [twentyhq/twenty@fc90b4b](https://github.com/twentyhq/twenty/commit/fc90b4ba8bb0a5d7c12c846fe9b2305527a0f7a8) | -| Packages parsed (CVE Lite) | 5,451 | -| Unique vulnerable packages (CVE Lite) | 105 | -| OSV advisory matches (CVE Lite) | 167 | -| Fix command groups (CVE Lite) | 4 | -| First-pass coverage (CVE Lite) | 24 / 105 findings | -| Skipped findings with reason (CVE Lite) | 81 | +| Field | Value | +| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-06-09 | +| CLI version | v1.20.0 | +| CVE Lite command | `node dist/index.js examples/twenty --verbose --all --json` | +| yarn audit command | `yarn npm audit` / `yarn npm audit -A` (Yarn 4.13.0 — no audit suggestions on lockfile-only snapshot) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/twenty/yarn.lock` from [twentyhq/twenty@fc90b4b](https://github.com/twentyhq/twenty/commit/fc90b4ba8bb0a5d7c12c846fe9b2305527a0f7a8) | +| Packages parsed (CVE Lite) | 5,451 | +| Unique vulnerable packages (CVE Lite) | 105 | +| OSV advisory matches (CVE Lite) | 167 | +| Fix command groups (CVE Lite) | 4 | +| First-pass coverage (CVE Lite) | 24 / 105 findings | +| Skipped findings with reason (CVE Lite) | 81 | Reproduce CVE Lite locally from the repository root: @@ -193,113 +193,113 @@ All 105 baseline findings remain open at the time of this study. No remediation Full vulnerable package list from the verified scan on 2026-06-09 (revision `fc90b4b`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| @nyariv/sandboxjs | 0.8.25 | critical | unknown | 0.9.6 ⊘ | GHSA-2gg9-6p7w-6cpj, GHSA-58jh-xv4v-pcx4 | -| @vitest/browser | 4.0.18 | critical | unknown | 4.1.6 ⊘ | GHSA-2h32-95rg-cppp | -| form-data | 2.3.3 | critical | unknown | 2.5.4 ⊘ | GHSA-fjxv-7rqg-78g4 | -| form-data | 4.0.0 | critical | unknown | 4.0.4 ⊘ | GHSA-fjxv-7rqg-78g4 | -| happy-dom | 15.11.7 | critical | unknown | 20.8.9 ⊘ | GHSA-37j7-fg3j-429f, GHSA-6q6h-j7hj-3r64 | -| vitest | 4.0.18 | critical | unknown | 4.1.0 ⊘ | GHSA-5xrq-8626-4rwp | -| @babel/plugin-transform-modules-systemjs | 7.25.9 | high | transitive | 7.29.4 | GHSA-fv7c-fp4j-7gwp | -| @opentelemetry/auto-instrumentations-node | 0.60.1 | high | unknown | 0.75.0 ⊘ | GHSA-q7rr-3cgh-j5r3 | -| @opentelemetry/exporter-prometheus | 0.202.0 | high | unknown | 0.217.0 ⊘ | GHSA-q7rr-3cgh-j5r3 | -| @opentelemetry/exporter-prometheus | 0.211.0 | high | unknown | 0.217.0 ⊘ | GHSA-q7rr-3cgh-j5r3 | -| @opentelemetry/sdk-node | 0.202.0 | high | unknown | 0.217.0 ⊘ | GHSA-q7rr-3cgh-j5r3 | -| axios | 1.13.6 | high | unknown | 1.16.0 ⊘ | GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw | -| axios | 1.13.5 | high | transitive | 1.16.0 | GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw | -| defu | 6.1.4 | high | unknown | 6.1.5 ⊘ | GHSA-737v-mqg7-c878 | -| electron | 36.0.1 | high | unknown | 39.8.5 ⊘ | GHSA-3c8v-cfp5-9885, GHSA-4p4r-m79c-wq3v | -| fast-uri | 3.0.1 | high | transitive | 3.1.2 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | -| fast-xml-builder | 1.0.0 | high | unknown | 1.1.7 ⊘ | GHSA-5wm8-gmm8-39j9 | -| fast-xml-parser | 5.4.1 | high | unknown | 5.7.0 ⊘ | GHSA-8gc5-j5rx-235r, GHSA-gh4j-gqv2-49f6 | -| immutable | 3.7.6 | high | unknown | 3.8.3 ⊘ | GHSA-wf6x-7x77-mvgw | -| koa | 3.0.3 | high | transitive | 3.1.2 | GHSA-7gcc-r8m5-44qm | -| lodash | 4.17.23 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | -| lodash | 4.17.21 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | -| minimatch | 9.0.3 | high | unknown | 9.0.7 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 10.0.3 | high | unknown | 10.2.3 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 3.1.2 | high | unknown | 3.1.4 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 4.2.3 | high | unknown | 4.2.5 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 7.4.6 | high | transitive | 7.4.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| minimatch | 3.0.8 | high | unknown | 3.1.4 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | -| next | 16.0.10 | high | unknown | 16.2.6 ⊘ | GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6 | -| next | 16.1.7 | high | unknown | 16.2.6 ⊘ | GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6 | -| node-forge | 1.3.2 | high | unknown | 1.4.0 ⊘ | GHSA-2328-f5f3-gj25, GHSA-5m6q-g25r-mvwx | -| path-to-regexp | 8.3.0 | high | unknown | 8.4.0 ⊘ | GHSA-27v5-c462-wpq7, GHSA-j3q9-mxjg-w52f | -| path-to-regexp | 0.1.12 | high | transitive | 0.1.13 | GHSA-37ch-88jc-xwx2 | -| picomatch | 4.0.2 | high | transitive | 4.0.4 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | -| picomatch | 2.3.1 | high | transitive | 2.3.2 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | -| picomatch | 4.0.3 | high | transitive | 4.0.4 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | -| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | -| tar | 6.2.1 | high | transitive | 7.5.11 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28cx | -| tmp | 0.2.1 | high | unknown | 0.2.6 ⊘ | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | -| tmp | 0.0.33 | high | unknown | 0.2.6 ⊘ | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | -| tmp | 0.2.5 | high | transitive | 0.2.6 | GHSA-ph9p-34f9-6g65 | -| typeorm | 0.3.20 | high | unknown | 0.3.26 ⊘ | GHSA-q2pj-6v73-8rgj | -| undici | 5.29.0 | high | unknown | 6.24.0 ⊘ | GHSA-2mjp-6q6p-2qxm, GHSA-4992-7rv2-5pvq | -| ws | 8.13.0 | high | unknown | 8.20.1 ⊘ | GHSA-3h5v-q93c-6h6q, GHSA-58qx-3vcg-4xpx | -| ws | 8.16.0 | high | unknown | 8.20.1 ⊘ | GHSA-3h5v-q93c-6h6q, GHSA-58qx-3vcg-4xpx | -| yeoman-environment | 3.3.0 | high | unknown | 6.0.1 ⊘ | GHSA-vv9j-gjw2-j8wp | -| @nestjs/core | 11.1.16 | medium | unknown | 11.1.18 ⊘ | GHSA-36xv-jgw5-4q75 | -| @octokit/plugin-paginate-rest | 2.21.3 | medium | unknown | 9.2.2 ⊘ | GHSA-h5c3-5r3r-rr8q | -| @octokit/request | 5.6.3 | medium | unknown | 8.4.1 ⊘ | GHSA-rmvr-2pp2-xj38 | -| @octokit/request-error | 2.1.0 | medium | unknown | 5.1.1 ⊘ | GHSA-xx4v-prfh-6cgc | -| @protobufjs/utf8 | 1.1.0 | medium | unknown | 1.1.1 ⊘ | GHSA-q6x5-8v7m-xcrf | -| ajv | 8.13.0 | medium | unknown | 8.18.0 ⊘ | GHSA-2g4f-4pwh-qvx6 | -| ajv | 8.17.1 | medium | transitive | 8.18.0 | GHSA-2g4f-4pwh-qvx6 | -| ajv | 6.12.6 | medium | unknown | 6.14.0 ⊘ | GHSA-2g4f-4pwh-qvx6 | -| ajv | 7.2.4 | medium | unknown | 8.18.0 ⊘ | GHSA-2g4f-4pwh-qvx6 | -| ajv | 8.12.0 | medium | unknown | 8.18.0 ⊘ | GHSA-2g4f-4pwh-qvx6 | -| apollo-server-core | 3.13.0 | medium | unknown | 5.5.0 ⊘ | GHSA-9q82-xgwf-vj6h | -| bn.js | 4.12.0 | medium | unknown | 4.12.3 ⊘ | GHSA-378v-28hj-76wf | -| bn.js | 5.2.1 | medium | unknown | 5.2.3 ⊘ | GHSA-378v-28hj-76wf | -| brace-expansion | 5.0.5 | medium | unknown | 5.0.6 ⊘ | GHSA-jxxr-4gwj-5jf2 | -| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | -| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | GHSA-f886-m6hf-6m8v | -| brace-expansion | 5.0.3 | medium | transitive | 5.0.6 | GHSA-f886-m6hf-6m8v, GHSA-jxxr-4gwj-5jf2 | -| dompurify | 3.3.3 | medium | unknown | 3.4.0 ⊘ | GHSA-39q2-94rc-95cp, GHSA-crv5-9vww-q3g8 | -| esbuild | 0.21.5 | medium | unknown | 0.25.0 ⊘ | GHSA-67mh-4wv8-2f99 | -| file-type | 20.5.0 | medium | unknown | 21.3.2 ⊘ | GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v | -| file-type | 21.3.0 | medium | unknown | 21.3.2 ⊘ | GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v | -| file-type | 21.3.1 | medium | unknown | 21.3.2 ⊘ | GHSA-j47w-4g3g-c36v | -| follow-redirects | 1.15.6 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | -| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | -| got | 9.6.0 | medium | unknown | 11.8.5 ⊘ | GHSA-pfrx-2q88-qq97 | -| ip-address | 10.0.1 | medium | unknown | 10.1.1 ⊘ | GHSA-v2v4-37r5-5v8g | -| ip-address | 9.0.5 | medium | transitive | 10.1.1 | GHSA-v2v4-37r5-5v8g | -| nodemailer | 8.0.4 | medium | unknown | 8.0.5 ⊘ | GHSA-vvjj-xcjg-gr5g | -| postcss | 8.4.31 | medium | unknown | 8.5.10 ⊘ | GHSA-qx2v-qp2m-jg93 | -| postcss | 8.4.49 | medium | unknown | 8.5.10 ⊘ | GHSA-qx2v-qp2m-jg93 | -| postcss | 8.5.8 | medium | unknown | 8.5.10 ⊘ | GHSA-qx2v-qp2m-jg93 | -| postcss | 8.5.6 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | -| qs | 6.15.0 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | -| qs | 6.14.2 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | -| qs | 6.5.5 | medium | unknown | 6.14.1 ⊘ | GHSA-6rw7-vpxm-498p | -| react-router | 6.30.3 | medium | unknown | 6.30.4 ⊘ | GHSA-2j2x-hqr9-3h42 | -| request | 2.88.2 | medium | unknown | 3.0.0 ⊘ | GHSA-p8p7-x288-28g6 | -| simplemde | 1.11.2 | medium | unknown | ⚠ no fix | GHSA-wg85-p6j7-gp3w | -| tough-cookie | 2.5.0 | medium | unknown | 4.1.3 ⊘ | GHSA-72xf-g2v4-qvf3 | -| unhead | 1.11.20 | medium | unknown | 2.1.13 ⊘ | GHSA-5339-hvwr-7582, GHSA-95h2-gj7x-gx9w | -| uuid | 9.0.1 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | -| uuid | 3.4.0 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | -| uuid | 8.3.2 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | -| uuid | 10.0.0 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | -| uuid | 11.1.0 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | -| uuid | 13.0.0 | medium | unknown | 13.0.1 ⊘ | GHSA-w5hq-g745-h8pq | -| vue-template-compiler | 2.7.16 | medium | unknown | ⚠ no fix | GHSA-g3ch-rx76-35fx | -| webpack-dev-server | 4.15.2 | medium | unknown | 5.2.4 ⊘ | GHSA-4v9v-hfq4-rm2v, GHSA-79cf-xcqc-c78w | -| ws | 8.17.1 | medium | unknown | 8.20.1 ⊘ | GHSA-58qx-3vcg-4xpx | -| ws | 8.18.0 | medium | transitive | 8.20.1 | GHSA-58qx-3vcg-4xpx | -| ws | 8.18.2 | medium | unknown | 8.20.1 ⊘ | GHSA-58qx-3vcg-4xpx | -| ws | 8.19.0 | medium | unknown | 8.20.1 ⊘ | GHSA-58qx-3vcg-4xpx | -| yaml | 1.10.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | -| yaml | 2.8.1 | medium | transitive | 2.8.3 | GHSA-48c2-rrv3-qjmp | -| @tootallnate/once | 1.1.2 | low | unknown | 2.0.1 ⊘ | GHSA-vpq2-c234-7xj6 | -| @tootallnate/once | 2.0.0 | low | unknown | 2.0.1 ⊘ | GHSA-vpq2-c234-7xj6 | -| diff | 4.0.2 | low | unknown | 4.0.4 ⊘ | GHSA-73rr-hh4g-fpgx | -| diff | 5.2.0 | low | unknown | 5.2.2 ⊘ | GHSA-73rr-hh4g-fpgx | -| elliptic | 6.6.1 | low | unknown | ⚠ no fix | GHSA-848j-6mx2-7j84 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ----------------------------------------- | ------- | -------- | ------------ | --------- | ---------------------------------------- | +| @nyariv/sandboxjs | 0.8.25 | critical | unknown | 0.9.6 ⊘ | GHSA-2gg9-6p7w-6cpj, GHSA-58jh-xv4v-pcx4 | +| @vitest/browser | 4.0.18 | critical | unknown | 4.1.6 ⊘ | GHSA-2h32-95rg-cppp | +| form-data | 2.3.3 | critical | unknown | 2.5.4 ⊘ | GHSA-fjxv-7rqg-78g4 | +| form-data | 4.0.0 | critical | unknown | 4.0.4 ⊘ | GHSA-fjxv-7rqg-78g4 | +| happy-dom | 15.11.7 | critical | unknown | 20.8.9 ⊘ | GHSA-37j7-fg3j-429f, GHSA-6q6h-j7hj-3r64 | +| vitest | 4.0.18 | critical | unknown | 4.1.0 ⊘ | GHSA-5xrq-8626-4rwp | +| @babel/plugin-transform-modules-systemjs | 7.25.9 | high | transitive | 7.29.4 | GHSA-fv7c-fp4j-7gwp | +| @opentelemetry/auto-instrumentations-node | 0.60.1 | high | unknown | 0.75.0 ⊘ | GHSA-q7rr-3cgh-j5r3 | +| @opentelemetry/exporter-prometheus | 0.202.0 | high | unknown | 0.217.0 ⊘ | GHSA-q7rr-3cgh-j5r3 | +| @opentelemetry/exporter-prometheus | 0.211.0 | high | unknown | 0.217.0 ⊘ | GHSA-q7rr-3cgh-j5r3 | +| @opentelemetry/sdk-node | 0.202.0 | high | unknown | 0.217.0 ⊘ | GHSA-q7rr-3cgh-j5r3 | +| axios | 1.13.6 | high | unknown | 1.16.0 ⊘ | GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw | +| axios | 1.13.5 | high | transitive | 1.16.0 | GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw | +| defu | 6.1.4 | high | unknown | 6.1.5 ⊘ | GHSA-737v-mqg7-c878 | +| electron | 36.0.1 | high | unknown | 39.8.5 ⊘ | GHSA-3c8v-cfp5-9885, GHSA-4p4r-m79c-wq3v | +| fast-uri | 3.0.1 | high | transitive | 3.1.2 | GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc | +| fast-xml-builder | 1.0.0 | high | unknown | 1.1.7 ⊘ | GHSA-5wm8-gmm8-39j9 | +| fast-xml-parser | 5.4.1 | high | unknown | 5.7.0 ⊘ | GHSA-8gc5-j5rx-235r, GHSA-gh4j-gqv2-49f6 | +| immutable | 3.7.6 | high | unknown | 3.8.3 ⊘ | GHSA-wf6x-7x77-mvgw | +| koa | 3.0.3 | high | transitive | 3.1.2 | GHSA-7gcc-r8m5-44qm | +| lodash | 4.17.23 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | +| lodash | 4.17.21 | high | transitive | 4.18.0 | GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc | +| minimatch | 9.0.3 | high | unknown | 9.0.7 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 10.0.3 | high | unknown | 10.2.3 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 3.1.2 | high | unknown | 3.1.4 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 4.2.3 | high | unknown | 4.2.5 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 7.4.6 | high | transitive | 7.4.8 | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| minimatch | 3.0.8 | high | unknown | 3.1.4 ⊘ | GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26 | +| next | 16.0.10 | high | unknown | 16.2.6 ⊘ | GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6 | +| next | 16.1.7 | high | unknown | 16.2.6 ⊘ | GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6 | +| node-forge | 1.3.2 | high | unknown | 1.4.0 ⊘ | GHSA-2328-f5f3-gj25, GHSA-5m6q-g25r-mvwx | +| path-to-regexp | 8.3.0 | high | unknown | 8.4.0 ⊘ | GHSA-27v5-c462-wpq7, GHSA-j3q9-mxjg-w52f | +| path-to-regexp | 0.1.12 | high | transitive | 0.1.13 | GHSA-37ch-88jc-xwx2 | +| picomatch | 4.0.2 | high | transitive | 4.0.4 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | +| picomatch | 2.3.1 | high | transitive | 2.3.2 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | +| picomatch | 4.0.3 | high | transitive | 4.0.4 | GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj | +| serialize-javascript | 6.0.2 | high | transitive | 7.0.5 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v | +| tar | 6.2.1 | high | transitive | 7.5.11 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28cx | +| tmp | 0.2.1 | high | unknown | 0.2.6 ⊘ | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | +| tmp | 0.0.33 | high | unknown | 0.2.6 ⊘ | GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 | +| tmp | 0.2.5 | high | transitive | 0.2.6 | GHSA-ph9p-34f9-6g65 | +| typeorm | 0.3.20 | high | unknown | 0.3.26 ⊘ | GHSA-q2pj-6v73-8rgj | +| undici | 5.29.0 | high | unknown | 6.24.0 ⊘ | GHSA-2mjp-6q6p-2qxm, GHSA-4992-7rv2-5pvq | +| ws | 8.13.0 | high | unknown | 8.20.1 ⊘ | GHSA-3h5v-q93c-6h6q, GHSA-58qx-3vcg-4xpx | +| ws | 8.16.0 | high | unknown | 8.20.1 ⊘ | GHSA-3h5v-q93c-6h6q, GHSA-58qx-3vcg-4xpx | +| yeoman-environment | 3.3.0 | high | unknown | 6.0.1 ⊘ | GHSA-vv9j-gjw2-j8wp | +| @nestjs/core | 11.1.16 | medium | unknown | 11.1.18 ⊘ | GHSA-36xv-jgw5-4q75 | +| @octokit/plugin-paginate-rest | 2.21.3 | medium | unknown | 9.2.2 ⊘ | GHSA-h5c3-5r3r-rr8q | +| @octokit/request | 5.6.3 | medium | unknown | 8.4.1 ⊘ | GHSA-rmvr-2pp2-xj38 | +| @octokit/request-error | 2.1.0 | medium | unknown | 5.1.1 ⊘ | GHSA-xx4v-prfh-6cgc | +| @protobufjs/utf8 | 1.1.0 | medium | unknown | 1.1.1 ⊘ | GHSA-q6x5-8v7m-xcrf | +| ajv | 8.13.0 | medium | unknown | 8.18.0 ⊘ | GHSA-2g4f-4pwh-qvx6 | +| ajv | 8.17.1 | medium | transitive | 8.18.0 | GHSA-2g4f-4pwh-qvx6 | +| ajv | 6.12.6 | medium | unknown | 6.14.0 ⊘ | GHSA-2g4f-4pwh-qvx6 | +| ajv | 7.2.4 | medium | unknown | 8.18.0 ⊘ | GHSA-2g4f-4pwh-qvx6 | +| ajv | 8.12.0 | medium | unknown | 8.18.0 ⊘ | GHSA-2g4f-4pwh-qvx6 | +| apollo-server-core | 3.13.0 | medium | unknown | 5.5.0 ⊘ | GHSA-9q82-xgwf-vj6h | +| bn.js | 4.12.0 | medium | unknown | 4.12.3 ⊘ | GHSA-378v-28hj-76wf | +| bn.js | 5.2.1 | medium | unknown | 5.2.3 ⊘ | GHSA-378v-28hj-76wf | +| brace-expansion | 5.0.5 | medium | unknown | 5.0.6 ⊘ | GHSA-jxxr-4gwj-5jf2 | +| brace-expansion | 1.1.12 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v | +| brace-expansion | 2.0.2 | medium | transitive | 2.0.3 | GHSA-f886-m6hf-6m8v | +| brace-expansion | 5.0.3 | medium | transitive | 5.0.6 | GHSA-f886-m6hf-6m8v, GHSA-jxxr-4gwj-5jf2 | +| dompurify | 3.3.3 | medium | unknown | 3.4.0 ⊘ | GHSA-39q2-94rc-95cp, GHSA-crv5-9vww-q3g8 | +| esbuild | 0.21.5 | medium | unknown | 0.25.0 ⊘ | GHSA-67mh-4wv8-2f99 | +| file-type | 20.5.0 | medium | unknown | 21.3.2 ⊘ | GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v | +| file-type | 21.3.0 | medium | unknown | 21.3.2 ⊘ | GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v | +| file-type | 21.3.1 | medium | unknown | 21.3.2 ⊘ | GHSA-j47w-4g3g-c36v | +| follow-redirects | 1.15.6 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | +| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 | +| got | 9.6.0 | medium | unknown | 11.8.5 ⊘ | GHSA-pfrx-2q88-qq97 | +| ip-address | 10.0.1 | medium | unknown | 10.1.1 ⊘ | GHSA-v2v4-37r5-5v8g | +| ip-address | 9.0.5 | medium | transitive | 10.1.1 | GHSA-v2v4-37r5-5v8g | +| nodemailer | 8.0.4 | medium | unknown | 8.0.5 ⊘ | GHSA-vvjj-xcjg-gr5g | +| postcss | 8.4.31 | medium | unknown | 8.5.10 ⊘ | GHSA-qx2v-qp2m-jg93 | +| postcss | 8.4.49 | medium | unknown | 8.5.10 ⊘ | GHSA-qx2v-qp2m-jg93 | +| postcss | 8.5.8 | medium | unknown | 8.5.10 ⊘ | GHSA-qx2v-qp2m-jg93 | +| postcss | 8.5.6 | medium | transitive | 8.5.10 | GHSA-qx2v-qp2m-jg93 | +| qs | 6.15.0 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | +| qs | 6.14.2 | medium | transitive | 6.15.2 | GHSA-q8mj-m7cp-5q26 | +| qs | 6.5.5 | medium | unknown | 6.14.1 ⊘ | GHSA-6rw7-vpxm-498p | +| react-router | 6.30.3 | medium | unknown | 6.30.4 ⊘ | GHSA-2j2x-hqr9-3h42 | +| request | 2.88.2 | medium | unknown | 3.0.0 ⊘ | GHSA-p8p7-x288-28g6 | +| simplemde | 1.11.2 | medium | unknown | ⚠ no fix | GHSA-wg85-p6j7-gp3w | +| tough-cookie | 2.5.0 | medium | unknown | 4.1.3 ⊘ | GHSA-72xf-g2v4-qvf3 | +| unhead | 1.11.20 | medium | unknown | 2.1.13 ⊘ | GHSA-5339-hvwr-7582, GHSA-95h2-gj7x-gx9w | +| uuid | 9.0.1 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | +| uuid | 3.4.0 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | +| uuid | 8.3.2 | medium | transitive | 11.1.1 | GHSA-w5hq-g745-h8pq | +| uuid | 10.0.0 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | +| uuid | 11.1.0 | medium | unknown | 11.1.1 ⊘ | GHSA-w5hq-g745-h8pq | +| uuid | 13.0.0 | medium | unknown | 13.0.1 ⊘ | GHSA-w5hq-g745-h8pq | +| vue-template-compiler | 2.7.16 | medium | unknown | ⚠ no fix | GHSA-g3ch-rx76-35fx | +| webpack-dev-server | 4.15.2 | medium | unknown | 5.2.4 ⊘ | GHSA-4v9v-hfq4-rm2v, GHSA-79cf-xcqc-c78w | +| ws | 8.17.1 | medium | unknown | 8.20.1 ⊘ | GHSA-58qx-3vcg-4xpx | +| ws | 8.18.0 | medium | transitive | 8.20.1 | GHSA-58qx-3vcg-4xpx | +| ws | 8.18.2 | medium | unknown | 8.20.1 ⊘ | GHSA-58qx-3vcg-4xpx | +| ws | 8.19.0 | medium | unknown | 8.20.1 ⊘ | GHSA-58qx-3vcg-4xpx | +| yaml | 1.10.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp | +| yaml | 2.8.1 | medium | transitive | 2.8.3 | GHSA-48c2-rrv3-qjmp | +| @tootallnate/once | 1.1.2 | low | unknown | 2.0.1 ⊘ | GHSA-vpq2-c234-7xj6 | +| @tootallnate/once | 2.0.0 | low | unknown | 2.0.1 ⊘ | GHSA-vpq2-c234-7xj6 | +| diff | 4.0.2 | low | unknown | 4.0.4 ⊘ | GHSA-73rr-hh4g-fpgx | +| diff | 5.2.0 | low | unknown | 5.2.2 ⊘ | GHSA-73rr-hh4g-fpgx | +| elliptic | 6.6.1 | low | unknown | ⚠ no fix | GHSA-848j-6mx2-7j84 | --- diff --git a/website/docs/case-studies/vercel-ai-sdk.md b/website/docs/case-studies/vercel-ai-sdk.md index d5a598b7..e91bec71 100644 --- a/website/docs/case-studies/vercel-ai-sdk.md +++ b/website/docs/case-studies/vercel-ai-sdk.md @@ -45,19 +45,19 @@ The five generated command groups illustrate CVE Lite's strength on pnpm monorep Both tools were run against the same `pnpm-lock.yaml` on the same machine on 2026-05-29. -| Metric | pnpm audit (10.33.4) | CVE Lite CLI v1.18.1 | -|---|---:|---:| -| Packages audited / parsed | 3,793 | 3,570 | -| Total reported findings | 162 | 55 | -| Critical | 2 | 2 | -| High | 56 | 22 | -| Moderate / Medium | 82 | 27 | -| Low | 22 | 4 | -| Direct vs transitive breakdown | ✗ | ✓ (3 / 52) | -| Deduplicated package view | ✗ | ✓ | -| Validated fix targets | partial | ✓ | -| Copy-and-run command groups | ✗ | ✓ (5 groups) | -| Skipped findings with reason | ✗ | ✓ (49 entries) | +| Metric | pnpm audit (10.33.4) | CVE Lite CLI v1.18.1 | +| ------------------------------ | -------------------: | -------------------: | +| Packages audited / parsed | 3,793 | 3,570 | +| Total reported findings | 162 | 55 | +| Critical | 2 | 2 | +| High | 56 | 22 | +| Moderate / Medium | 82 | 27 | +| Low | 22 | 4 | +| Direct vs transitive breakdown | ✗ | ✓ (3 / 52) | +| Deduplicated package view | ✗ | ✓ | +| Validated fix targets | partial | ✓ | +| Copy-and-run command groups | ✗ | ✓ (5 groups) | +| Skipped findings with reason | ✗ | ✓ (49 entries) | **Why the totals differ:** `pnpm audit` counts **162 vulnerability entries** (advisory × path rows across example workspaces). CVE Lite reports **55 unique vulnerable package versions** once each. Multiple OpenTelemetry or `undici` paths collapse into single package-level decisions. @@ -81,9 +81,9 @@ pnpm add --filter ./examples/next --filter ./examples/next-agent … next@15.5.1 No remediation pass was performed for this study. This table records the verified baseline scan only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 55 | 2 | 22 | 27 | 4 | 3 | 52 | 5 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 55 | 2 | 22 | 27 | 4 | 3 | 52 | 5 | --- @@ -123,20 +123,20 @@ The example lockfile reflects `vercel/ai` at revision `3215032043569f75a97fadf2b Every number in this case study comes from a live scan of the committed fixture at `examples/vercel-ai-sdk/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-05-29 | -| CLI version | v1.18.1 | -| CVE Lite command | `node dist/index.js examples/vercel-ai-sdk --verbose --all --json` | -| pnpm audit command | `pnpm audit` / `pnpm audit --json` (pnpm 10.33.4) | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/vercel-ai-sdk/pnpm-lock.yaml` from [vercel/ai@3215032](https://github.com/vercel/ai/commit/3215032043569f75a97fadf2b08aa38f11b011af) | -| Packages parsed (CVE Lite) | 3,570 | -| Unique vulnerable packages (CVE Lite) | 55 | -| Vulnerability entries (pnpm audit) | 162 | -| Fix command groups (CVE Lite) | 5 | -| First-pass covered findings (CVE Lite) | 6 | -| Skipped findings with reason (CVE Lite) | 49 | +| Field | Value | +| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-05-29 | +| CLI version | v1.18.1 | +| CVE Lite command | `node dist/index.js examples/vercel-ai-sdk --verbose --all --json` | +| pnpm audit command | `pnpm audit` / `pnpm audit --json` (pnpm 10.33.4) | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/vercel-ai-sdk/pnpm-lock.yaml` from [vercel/ai@3215032](https://github.com/vercel/ai/commit/3215032043569f75a97fadf2b08aa38f11b011af) | +| Packages parsed (CVE Lite) | 3,570 | +| Unique vulnerable packages (CVE Lite) | 55 | +| Vulnerability entries (pnpm audit) | 162 | +| Fix command groups (CVE Lite) | 5 | +| First-pass covered findings (CVE Lite) | 6 | +| Skipped findings with reason (CVE Lite) | 49 | Reproduce CVE Lite locally from the repository root: @@ -175,63 +175,63 @@ Only **6 findings** have first-pass copy-and-run commands. The other **49** requ Full vulnerable package list from the verified scan on 2026-05-29 (revision `3215032`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| next | 15.0.7 | critical | direct | 12.3.5 | CVE-2026-44573, CVE-2026-44572… | -| protobufjs | 8.0.0 | critical | transitive | 8.2.0 | CVE-2026-44294, CVE-2026-44293… | -| @hono/node-server | 1.13.7 | high | transitive | 1.19.13 | CVE-2026-39406, CVE-2026-29087 | -| @opentelemetry/auto-instrumentations-node | 0.54.0 | high | transitive | 0.75.0 | CVE-2026-44902 | -| @opentelemetry/exporter-prometheus | 0.210.0 | high | transitive | 0.217.0 | CVE-2026-44902 | -| @opentelemetry/sdk-node | 0.210.0 | high | transitive | 0.217.0 | CVE-2026-44902 | -| @opentelemetry/sdk-node | 0.56.0 | high | transitive | 0.217.0 | CVE-2026-44902 | -| devalue | 5.6.3 | high | transitive | 5.8.1 | CVE-2026-42570, CVE-2026-30226 | -| effect | 3.18.4 | high | transitive | 3.20.0 | CVE-2026-32887 | -| fastify | 5.1.0 | high | transitive | 5.8.3 | CVE-2026-3635, CVE-2026-25223… | -| glob | 10.4.5 | high | transitive | 10.5.0 | CVE-2025-64756 | -| hono | 4.6.9 | high | transitive | 4.12.18 | CVE-2026-22818, CVE-2026-29086… | -| js-cookie | 3.0.6 | high | transitive | 3.0.7 | CVE-2026-46625 | -| minimatch | 3.1.2 | high | transitive | 3.1.4 | CVE-2026-27904, CVE-2026-26996… | -| multer | 2.0.2 | high | transitive | 2.1.1 | CVE-2026-3520, CVE-2026-2359… | -| next | 15.5.9 | high | direct | 15.5.18 | CVE-2026-44575, CVE-2026-45109… | -| nuxt | 3.14.159 | high | transitive | 3.21.6 | CVE-2026-45669, CVE-2026-46342… | -| picomatch | 4.0.1 | high | transitive | 4.0.4 | CVE-2026-33672, CVE-2026-33671 | -| tar | 6.2.1 | high | transitive | 7.5.11 | CVE-2026-24842, CVE-2026-26960… | -| tmp | 0.0.33 | high | transitive | 0.2.6 | CVE-2025-54798, CVE-2026-44705 | -| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | -| undici | 5.29.0 | high | transitive | 6.24.0 | CVE-2026-1525, CVE-2026-1527… | -| undici | 7.22.0 | high | transitive | 7.24.0 | CVE-2026-1525, CVE-2026-1527… | -| valibot | 1.1.0 | high | transitive | 1.2.0 | CVE-2025-66020 | -| @nestjs/core | 10.4.22 | medium | transitive | 11.1.18 | CVE-2026-35515 | -| @nuxt/devtools | 1.6.3 | medium | transitive | 2.6.4 | CVE-2025-52662 | -| @nuxt/vite-builder | 3.14.159 | medium | transitive | 3.15.3 | CVE-2025-24360 | -| ajv | 8.12.0 | medium | transitive | 8.18.0 | CVE-2025-69873 | -| brace-expansion | 1.1.11 | medium | transitive | 1.1.13 | CVE-2026-33750, CVE-2025-5889 | -| esbuild | 0.18.20 | medium | transitive | 0.25.0 | — | -| esbuild | 0.21.5 | medium | transitive | 0.25.0 | — | -| esbuild | 0.23.1 | medium | transitive | 0.25.0 | — | -| esbuild | 0.24.2 | medium | transitive | 0.25.0 | — | -| file-type | 16.5.4 | medium | transitive | 21.3.1 | CVE-2026-31808 | -| file-type | 18.7.0 | medium | transitive | 21.3.1 | CVE-2026-31808 | -| file-type | 20.4.1 | medium | transitive | 21.3.2 | CVE-2026-31808, CVE-2026-32630 | -| js-yaml | 4.1.0 | medium | transitive | 4.1.1 | CVE-2025-64718 | -| nanotar | 0.1.1 | medium | transitive | 0.2.1 | CVE-2025-69874 | -| phin | 2.9.3 | medium | transitive | 3.7.1 | — | -| postcss | 8.4.31 | medium | transitive | 8.5.10 | CVE-2026-41305 | -| qs | 6.13.0 | medium | transitive | 6.15.2 | CVE-2025-15284, CVE-2026-8723… | -| qs | 6.14.2 | medium | transitive | 6.15.2 | CVE-2026-8723 | -| qs | 6.15.1 | medium | transitive | 6.15.2 | CVE-2026-8723 | -| turbo | 2.4.4 | medium | direct | 2.9.14 | CVE-2026-45772, CVE-2026-45773 | -| unhead | 1.11.20 | medium | transitive | 2.1.13 | CVE-2026-31873, CVE-2026-39315… | -| uuid | 10.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| uuid | 9.0.1 | medium | transitive | 11.1.1 | CVE-2026-41907 | -| vite | 5.4.21 | medium | transitive | 6.4.2 | CVE-2026-39365 | -| webpack-dev-server | 5.2.2 | medium | transitive | 5.2.4 | CVE-2026-6402 | -| yaml | 2.7.0 | medium | transitive | 2.8.3 | CVE-2026-33532 | -| cookie | 0.6.0 | low | transitive | 0.7.0 | CVE-2024-47764 | -| diff | 7.0.0 | low | transitive | 8.0.3 | CVE-2026-24001 | -| tsup | 7.2.0 | low | transitive | 8.3.5 | CVE-2024-53384 | -| webpack | 5.97.1 | low | transitive | 5.104.1 | CVE-2025-68157, CVE-2025-68458 | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ----------------------------------------- | -------- | -------- | ------------ | -------- | ------------------------------- | +| next | 15.0.7 | critical | direct | 12.3.5 | CVE-2026-44573, CVE-2026-44572… | +| protobufjs | 8.0.0 | critical | transitive | 8.2.0 | CVE-2026-44294, CVE-2026-44293… | +| @hono/node-server | 1.13.7 | high | transitive | 1.19.13 | CVE-2026-39406, CVE-2026-29087 | +| @opentelemetry/auto-instrumentations-node | 0.54.0 | high | transitive | 0.75.0 | CVE-2026-44902 | +| @opentelemetry/exporter-prometheus | 0.210.0 | high | transitive | 0.217.0 | CVE-2026-44902 | +| @opentelemetry/sdk-node | 0.210.0 | high | transitive | 0.217.0 | CVE-2026-44902 | +| @opentelemetry/sdk-node | 0.56.0 | high | transitive | 0.217.0 | CVE-2026-44902 | +| devalue | 5.6.3 | high | transitive | 5.8.1 | CVE-2026-42570, CVE-2026-30226 | +| effect | 3.18.4 | high | transitive | 3.20.0 | CVE-2026-32887 | +| fastify | 5.1.0 | high | transitive | 5.8.3 | CVE-2026-3635, CVE-2026-25223… | +| glob | 10.4.5 | high | transitive | 10.5.0 | CVE-2025-64756 | +| hono | 4.6.9 | high | transitive | 4.12.18 | CVE-2026-22818, CVE-2026-29086… | +| js-cookie | 3.0.6 | high | transitive | 3.0.7 | CVE-2026-46625 | +| minimatch | 3.1.2 | high | transitive | 3.1.4 | CVE-2026-27904, CVE-2026-26996… | +| multer | 2.0.2 | high | transitive | 2.1.1 | CVE-2026-3520, CVE-2026-2359… | +| next | 15.5.9 | high | direct | 15.5.18 | CVE-2026-44575, CVE-2026-45109… | +| nuxt | 3.14.159 | high | transitive | 3.21.6 | CVE-2026-45669, CVE-2026-46342… | +| picomatch | 4.0.1 | high | transitive | 4.0.4 | CVE-2026-33672, CVE-2026-33671 | +| tar | 6.2.1 | high | transitive | 7.5.11 | CVE-2026-24842, CVE-2026-26960… | +| tmp | 0.0.33 | high | transitive | 0.2.6 | CVE-2025-54798, CVE-2026-44705 | +| tmp | 0.2.5 | high | transitive | 0.2.6 | CVE-2026-44705 | +| undici | 5.29.0 | high | transitive | 6.24.0 | CVE-2026-1525, CVE-2026-1527… | +| undici | 7.22.0 | high | transitive | 7.24.0 | CVE-2026-1525, CVE-2026-1527… | +| valibot | 1.1.0 | high | transitive | 1.2.0 | CVE-2025-66020 | +| @nestjs/core | 10.4.22 | medium | transitive | 11.1.18 | CVE-2026-35515 | +| @nuxt/devtools | 1.6.3 | medium | transitive | 2.6.4 | CVE-2025-52662 | +| @nuxt/vite-builder | 3.14.159 | medium | transitive | 3.15.3 | CVE-2025-24360 | +| ajv | 8.12.0 | medium | transitive | 8.18.0 | CVE-2025-69873 | +| brace-expansion | 1.1.11 | medium | transitive | 1.1.13 | CVE-2026-33750, CVE-2025-5889 | +| esbuild | 0.18.20 | medium | transitive | 0.25.0 | — | +| esbuild | 0.21.5 | medium | transitive | 0.25.0 | — | +| esbuild | 0.23.1 | medium | transitive | 0.25.0 | — | +| esbuild | 0.24.2 | medium | transitive | 0.25.0 | — | +| file-type | 16.5.4 | medium | transitive | 21.3.1 | CVE-2026-31808 | +| file-type | 18.7.0 | medium | transitive | 21.3.1 | CVE-2026-31808 | +| file-type | 20.4.1 | medium | transitive | 21.3.2 | CVE-2026-31808, CVE-2026-32630 | +| js-yaml | 4.1.0 | medium | transitive | 4.1.1 | CVE-2025-64718 | +| nanotar | 0.1.1 | medium | transitive | 0.2.1 | CVE-2025-69874 | +| phin | 2.9.3 | medium | transitive | 3.7.1 | — | +| postcss | 8.4.31 | medium | transitive | 8.5.10 | CVE-2026-41305 | +| qs | 6.13.0 | medium | transitive | 6.15.2 | CVE-2025-15284, CVE-2026-8723… | +| qs | 6.14.2 | medium | transitive | 6.15.2 | CVE-2026-8723 | +| qs | 6.15.1 | medium | transitive | 6.15.2 | CVE-2026-8723 | +| turbo | 2.4.4 | medium | direct | 2.9.14 | CVE-2026-45772, CVE-2026-45773 | +| unhead | 1.11.20 | medium | transitive | 2.1.13 | CVE-2026-31873, CVE-2026-39315… | +| uuid | 10.0.0 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 8.3.2 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| uuid | 9.0.1 | medium | transitive | 11.1.1 | CVE-2026-41907 | +| vite | 5.4.21 | medium | transitive | 6.4.2 | CVE-2026-39365 | +| webpack-dev-server | 5.2.2 | medium | transitive | 5.2.4 | CVE-2026-6402 | +| yaml | 2.7.0 | medium | transitive | 2.8.3 | CVE-2026-33532 | +| cookie | 0.6.0 | low | transitive | 0.7.0 | CVE-2024-47764 | +| diff | 7.0.0 | low | transitive | 8.0.3 | CVE-2026-24001 | +| tsup | 7.2.0 | low | transitive | 8.3.5 | CVE-2024-53384 | +| webpack | 5.97.1 | low | transitive | 5.104.1 | CVE-2025-68157, CVE-2025-68458 | --- diff --git a/website/docs/case-studies/vscode.md b/website/docs/case-studies/vscode.md index 458df7db..68437d4c 100644 --- a/website/docs/case-studies/vscode.md +++ b/website/docs/case-studies/vscode.md @@ -46,19 +46,19 @@ The **direct/transitive split** and **dev/runtime labelling** are the headline f Both tools were run against the same root `package-lock.json` on the same machine on 2026-06-11. -| Metric | npm audit | CVE Lite CLI v1.21.0 | -|---|---:|---:| -| Total reported findings | 18 | 6 | -| Critical | 1 | 1 | -| High | 5 | 1 | -| Moderate / Medium | 12 | 4 | -| Low | 0 | 0 | -| Direct vs transitive breakdown | ✗ | ✓ (1 / 5) | -| Dev vs runtime labelling | ✗ | ✓ (3 dev, 3 runtime) | -| Deduplicated package view | ✗ | ✓ | -| Validated fix targets | partial | ✓ | -| Specific copy-and-run commands | partial (`npm audit fix`) | ✓ (4 groups) | -| Skipped findings with reason | ✗ | ✓ | +| Metric | npm audit | CVE Lite CLI v1.21.0 | +| ------------------------------ | ------------------------: | -------------------: | +| Total reported findings | 18 | 6 | +| Critical | 1 | 1 | +| High | 5 | 1 | +| Moderate / Medium | 12 | 4 | +| Low | 0 | 0 | +| Direct vs transitive breakdown | ✗ | ✓ (1 / 5) | +| Dev vs runtime labelling | ✗ | ✓ (3 dev, 3 runtime) | +| Deduplicated package view | ✗ | ✓ | +| Validated fix targets | partial | ✓ | +| Specific copy-and-run commands | partial (`npm audit fix`) | ✓ (4 groups) | +| Skipped findings with reason | ✗ | ✓ | **Why the totals differ:** @@ -87,9 +87,9 @@ npm install gulp@5.0.0 # medium: micromatch path-specific No remediation pass was performed for this study. VS Code's peer dependency graph prevents automated lockfile-only remediation. This table records the verified baseline only. -| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Baseline (verified) | 6 | 1 | 1 | 4 | 0 | 1 | 5 | 4 | +| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups | +| ------------------- | -------: | -------: | ---: | -----: | --: | -----: | ---------: | -------------: | +| Baseline (verified) | 6 | 1 | 1 | 4 | 0 | 1 | 5 | 4 | Four command groups covering four findings is strong first-pass coverage on a professionally maintained editor repository. The two remaining findings (`uuid@3.4.0` and the remaining `micromatch` paths) require maintainer-level dependency routing. @@ -133,19 +133,19 @@ The example lockfile reflects VS Code at revision `b867fed` (2026-06-11). VS Cod Every number in this case study comes from a live scan of the committed fixture at `examples/vscode/` in the CVE Lite CLI repository. -| Field | Value | -|---|---| -| Scan date | 2026-06-11 | -| CLI version | v1.21.0 | -| CVE Lite command | `cve-lite examples/vscode --verbose --all` | -| npm audit command | `npm audit` | -| Advisory source | OSV (`https://api.osv.dev`) — online mode | -| Lockfile source | `examples/vscode/package-lock.json` from [microsoft/vscode@b867fed](https://github.com/microsoft/vscode/commit/b867fed) | -| Packages parsed (CVE Lite) | 1,374 | -| Unique vulnerable packages (CVE Lite) | 6 | -| Vulnerability entries (npm audit) | 18 | -| Fix command groups (CVE Lite) | 4 | -| Findings covered by fix commands (CVE Lite) | 4 of 6 | +| Field | Value | +| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| Scan date | 2026-06-11 | +| CLI version | v1.21.0 | +| CVE Lite command | `cve-lite examples/vscode --verbose --all` | +| npm audit command | `npm audit` | +| Advisory source | OSV (`https://api.osv.dev`) — online mode | +| Lockfile source | `examples/vscode/package-lock.json` from [microsoft/vscode@b867fed](https://github.com/microsoft/vscode/commit/b867fed) | +| Packages parsed (CVE Lite) | 1,374 | +| Unique vulnerable packages (CVE Lite) | 6 | +| Vulnerability entries (npm audit) | 18 | +| Fix command groups (CVE Lite) | 4 | +| Findings covered by fix commands (CVE Lite) | 4 of 6 | --- @@ -165,14 +165,14 @@ All 6 baseline findings remain open at the time of this study. No remediation wa Full vulnerable package list from the verified scan on 2026-06-11 (revision `b867fed`): -| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | -|---|---|---|---|---|---| -| shell-quote | 1.8.3 | critical | transitive | 1.8.4 | GHSA-w7jw-789q-3m8p | -| braces | 2.3.2 | high | transitive · dev | 3.0.3 | GHSA-grv7-fg5c-xmjg | -| @anthropic-ai/sdk | 0.82.0 | medium | direct | 0.91.1 | GHSA-p7fg-763f-g4gf | -| micromatch | 3.1.10 | medium | transitive · dev | 4.0.8 | GHSA-952p-6rrq-rcjv | -| postcss | 7.0.39 | medium | transitive · dev | 8.5.10 | GHSA-7fh5-64p2-3v2j, GHSA-qx2v-qp2m-jg93 | -| uuid | 3.4.0 | medium | transitive | 7.0.0 | GHSA-w5hq-g745-h8pq | +| Package | Version | Severity | Relationship | Fix hint | Advisory IDs | +| ----------------- | ------- | -------- | ---------------- | -------- | ---------------------------------------- | +| shell-quote | 1.8.3 | critical | transitive | 1.8.4 | GHSA-w7jw-789q-3m8p | +| braces | 2.3.2 | high | transitive · dev | 3.0.3 | GHSA-grv7-fg5c-xmjg | +| @anthropic-ai/sdk | 0.82.0 | medium | direct | 0.91.1 | GHSA-p7fg-763f-g4gf | +| micromatch | 3.1.10 | medium | transitive · dev | 4.0.8 | GHSA-952p-6rrq-rcjv | +| postcss | 7.0.39 | medium | transitive · dev | 8.5.10 | GHSA-7fh5-64p2-3v2j, GHSA-qx2v-qp2m-jg93 | +| uuid | 3.4.0 | medium | transitive | 7.0.0 | GHSA-w5hq-g745-h8pq | --- diff --git a/website/docs/cli-reference.md b/website/docs/cli-reference.md index 37816212..0597ba10 100644 --- a/website/docs/cli-reference.md +++ b/website/docs/cli-reference.md @@ -17,35 +17,35 @@ cve-lite install-skill ## Scan options -| Flag | Default | Description | Example | -|---|---|---|---| -| `--prod-only` | off | Exclude dev dependencies from the scan | `cve-lite . --prod-only` | +| Flag | Default | Description | Example | +| ---------------- | -------- | ---------------------------------------------------------------------------------- | -------------------------------- | +| `--prod-only` | off | Exclude dev dependencies from the scan | `cve-lite . --prod-only` | | `--min-severity` | `medium` | Only show findings at or above this severity (`critical`, `high`, `medium`, `low`) | `cve-lite . --min-severity high` | -| `--all` | off | Show all findings including low and unknown; appends a full table in compact mode | `cve-lite . --all` | -| `--search-depth` | `4` | How many directory levels deep to search for a lockfile | `cve-lite . --search-depth 2` | -| `--batch-size` | `100` | Number of packages sent per OSV API request | `cve-lite . --batch-size 50` | +| `--all` | off | Show all findings including low and unknown; appends a full table in compact mode | `cve-lite . --all` | +| `--search-depth` | `4` | How many directory levels deep to search for a lockfile | `cve-lite . --search-depth 2` | +| `--batch-size` | `100` | Number of packages sent per OSV API request | `cve-lite . --batch-size 50` | --- ## Output options -| Flag | Default | Description | Example | -|---|---|---|---| -| `--verbose` | off | Full output: severity table, fix plan, findings table, coverage notes | `cve-lite . --verbose` | -| `--json` | off | Machine-readable JSON output (suppresses all other output) | `cve-lite . --json` | -| `--sarif` | off | Write SARIF 2.1.0 output to a timestamped `.sarif` file; can be combined with `--json` and `--report` | `cve-lite . --sarif` | -| `--cdx` | off | Write CycloneDX 1.4 SBOM to a timestamped `.cdx.json` file; can be combined with `--json` and `--sarif`; cannot be combined with `--report` | `cve-lite . --cdx` | +| Flag | Default | Description | Example | +| ------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- | +| `--verbose` | off | Full output: severity table, fix plan, findings table, coverage notes | `cve-lite . --verbose` | +| `--json` | off | Machine-readable JSON output (suppresses all other output) | `cve-lite . --json` | +| `--sarif` | off | Write SARIF 2.1.0 output to a timestamped `.sarif` file; can be combined with `--json` and `--report` | `cve-lite . --sarif` | +| `--cdx` | off | Write CycloneDX 1.4 SBOM to a timestamped `.cdx.json` file; can be combined with `--json` and `--sarif`; cannot be combined with `--report` | `cve-lite . --cdx` | | `--report[=]` | off / `./cve-report` | Generate an HTML report; optional path sets output directory (default `./cve-report`); opens in browser by default; cannot be used with `--json` | `cve-lite . --report`
    `cve-lite . --report ./reports` | -| `--no-open` | off | Generate the HTML report without opening it in the browser | `cve-lite . --report --no-open` | +| `--no-open` | off | Generate the HTML report without opening it in the browser | `cve-lite . --report --no-open` | --- ## Offline options -| Flag | Default | Description | Example | -|---|---|---|---| -| `--offline` | off | Use the local advisory DB only — no OSV API calls | `cve-lite . --offline` | -| `--offline-db=` | auto | Path to a specific advisory DB file | `cve-lite . --offline-db ./advisories.db` | +| Flag | Default | Description | Example | +| --------------------- | ------- | ------------------------------------------------- | ----------------------------------------- | +| `--offline` | off | Use the local advisory DB only — no OSV API calls | `cve-lite . --offline` | +| `--offline-db=` | auto | Path to a specific advisory DB file | `cve-lite . --offline-db ./advisories.db` | Sync the local advisory DB with: @@ -60,10 +60,10 @@ See [Offline Advisory DB](./offline-advisory-db.md) for the full offline workflo ## Network / SSL options -| Flag | Default | Description | Example | -|---|---|---|---| -| `--ca-cert=` | - | Path to a PEM CA certificate file for corporate SSL inspection proxies | `cve-lite . --ca-cert ~/corp-ca.crt` | -| `--osv-url=` | OSV API | Use a custom OSV-compatible endpoint instead of the public API | `cve-lite . --osv-url https://osv.example.com` | +| Flag | Default | Description | Example | +| ------------------ | ------- | ---------------------------------------------------------------------- | ---------------------------------------------- | +| `--ca-cert=` | - | Path to a PEM CA certificate file for corporate SSL inspection proxies | `cve-lite . --ca-cert ~/corp-ca.crt` | +| `--osv-url=` | OSV API | Use a custom OSV-compatible endpoint instead of the public API | `cve-lite . --osv-url https://osv.example.com` | For networks with SSL inspection, save the certificate path once so you do not need to pass the flag on every scan: @@ -77,12 +77,12 @@ See [Corporate SSL Proxy](./corporate-proxy.md) for the full setup workflow. ## CI / Automation options -| Flag | Default | Description | Example | -|---|---|---|---| -| `--fail-on` | `critical` | Exit with code `1` if any finding meets or exceeds this severity (`critical`, `high`, `medium`, `low`); exit `0` otherwise | `cve-lite . --fail-on high` | -| `--fix` | off | Auto-apply direct-dependency fix commands (direct deps only, v1); cannot be used with `--json` | `cve-lite . --fix` | -| `--usage` | off | Scan source files to detect which packages are actually imported | `cve-lite . --usage` | -| `--only-used` | off | Show only findings for packages that are imported in source code (implies `--usage`) | `cve-lite . --only-used` | +| Flag | Default | Description | Example | +| ------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `--fail-on` | `critical` | Exit with code `1` if any finding meets or exceeds this severity (`critical`, `high`, `medium`, `low`); exit `0` otherwise | `cve-lite . --fail-on high` | +| `--fix` | off | Auto-apply direct-dependency fix commands (direct deps only, v1); cannot be used with `--json` | `cve-lite . --fix` | +| `--usage` | off | Scan source files to detect which packages are actually imported | `cve-lite . --usage` | +| `--only-used` | off | Show only findings for packages that are imported in source code (implies `--usage`) | `cve-lite . --only-used` | **Note:** `--usage-hints` is a deprecated alias for `--usage`. @@ -92,10 +92,10 @@ See [Workflow Integration](./workflow-integration.md) for CI/CD patterns and Git ## Cache options -| Flag | Default | Description | Example | -|---|---|---|---| -| `--cache-dir=` | `~/.cache/cve-lite` | Use a specific directory for the advisory response cache | `cve-lite . --cache-dir ./.cache` | -| `--no-cache` | — | Skip the query cache and fetch fresh results from OSV for this scan | `cve-lite . --no-cache` | +| Flag | Default | Description | Example | +| -------------------- | ------------------- | ------------------------------------------------------------------- | --------------------------------- | +| `--cache-dir=` | `~/.cache/cve-lite` | Use a specific directory for the advisory response cache | `cve-lite . --cache-dir ./.cache` | +| `--no-cache` | — | Skip the query cache and fetch fresh results from OSV for this scan | `cve-lite . --no-cache` | To clear the cache manually, delete `~/.cache/cve-lite/osv-vulns.json`. The next scan will re-fetch advisories from OSV. @@ -115,8 +115,8 @@ cve-lite config show # Print current config and config file loca Manages persistent CLI configuration stored in `~/.cve-lite-cli/config.json`. Currently supports one key: -| Key | Description | -|---|---| +| Key | Description | +| --------- | ----------------------------------------------------------------- | | `ca-cert` | Path to a PEM CA certificate for corporate SSL inspection proxies | The file must be a valid PEM certificate (starting with `-----BEGIN CERTIFICATE-----`). CVE Lite CLI validates the file exists and is readable before saving. diff --git a/website/docs/comparison.md b/website/docs/comparison.md index 135252e4..44c0df5e 100644 --- a/website/docs/comparison.md +++ b/website/docs/comparison.md @@ -21,23 +21,23 @@ This page compares CVE Lite CLI against the tools developers most commonly consi ## Practical comparison -| Capability | CVE Lite CLI | Dependabot | npm audit | OSV-Scanner | Snyk CLI | Socket CLI | -|---|:---:|:---:|:---:|:---:|:---:|:---:| -| JS/TS lockfile scanning | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| npm + pnpm + Yarn support | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | -| Developer-time local scanning | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | -| No account or GitHub repo required | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | -| Works in any CI provider | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | -| Usage-aware reachability scanning | ✅ | ❌ | ❌ | ❌ | ✅ | ⚠️ | -| Direct vs transitive visibility | ✅ | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | -| Validated copy-and-run fix commands | ✅ | ❌ | ❌ | ❌ | ✅ | ⚠️ | -| Transitive parent update guidance | ✅ | ❌ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | -| Fix version validation before suggesting | ✅ | ❌ | ❌ | ❌ | ⚠️ | ❌ | -| Clear top-priority fix guidance | ✅ | ❌ | ❌ | ❌ | ✅ | ⚠️ | -| Suggested remediation plan | ✅ | ❌ | ❌ | ⚠️ | ✅ | ⚠️ | -| JSON + SARIF output | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | -| Offline/local advisory DB workflow | ✅ | ❌ | ❌ | ⚠️ | ❌ | ❌ | -| No automatic PR noise | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | +| Capability | CVE Lite CLI | Dependabot | npm audit | OSV-Scanner | Snyk CLI | Socket CLI | +| ---------------------------------------- | :----------: | :--------: | :-------: | :---------: | :------: | :--------: | +| JS/TS lockfile scanning | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| npm + pnpm + Yarn support | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | +| Developer-time local scanning | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | +| No account or GitHub repo required | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | +| Works in any CI provider | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | +| Usage-aware reachability scanning | ✅ | ❌ | ❌ | ❌ | ✅ | ⚠️ | +| Direct vs transitive visibility | ✅ | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | +| Validated copy-and-run fix commands | ✅ | ❌ | ❌ | ❌ | ✅ | ⚠️ | +| Transitive parent update guidance | ✅ | ❌ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | +| Fix version validation before suggesting | ✅ | ❌ | ❌ | ❌ | ⚠️ | ❌ | +| Clear top-priority fix guidance | ✅ | ❌ | ❌ | ❌ | ✅ | ⚠️ | +| Suggested remediation plan | ✅ | ❌ | ❌ | ⚠️ | ✅ | ⚠️ | +| JSON + SARIF output | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | +| Offline/local advisory DB workflow | ✅ | ❌ | ❌ | ⚠️ | ❌ | ❌ | +| No automatic PR noise | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ = built-in strength · ⚠️ = partial or workflow-dependent · ❌ = not a core strength @@ -47,10 +47,10 @@ Transitive parent update guidance is one of CVE Lite CLI's core differentiators. ## Offline support -| Capability | CVE Lite CLI | Dependabot | npm audit | OSV-Scanner | Snyk CLI | Socket CLI | -|---|:---:|:---:|:---:|:---:|:---:|:---:| -| Local advisory DB workflow | ✅ | ❌ | ❌ | ⚠️ | ❌ | ❌ | -| Zero runtime advisory API calls | ✅ | ❌ | ❌ | ⚠️ | ❌ | ❌ | +| Capability | CVE Lite CLI | Dependabot | npm audit | OSV-Scanner | Snyk CLI | Socket CLI | +| ------------------------------- | :----------: | :--------: | :-------: | :---------: | :------: | :--------: | +| Local advisory DB workflow | ✅ | ❌ | ❌ | ⚠️ | ❌ | ❌ | +| Zero runtime advisory API calls | ✅ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ✅ = built-in strength · ⚠️ = partial or workflow-dependent · ❌ = not a core strength @@ -130,6 +130,7 @@ CVE Lite validates the suggested fix version against OSV before presenting it. Y CVE Lite identifies the vulnerable package, traces it to the parent that introduced it, and hands you the upgrade command for the parent. For npm lockfiles it goes one step further: if the current parent version range can already absorb a non-vulnerable child version, it suggests `npm update ` instead of a full version bump. **npm audit output — transitive finding:** + ``` path-to-regexp 0.2.0 - 1.8.0 Severity: high @@ -144,6 +145,7 @@ To address all issues, run: npm audit fix --force ``` **CVE Lite CLI output — same project:** + ``` HIGH path-to-regexp@1.7.0 Transitive dependency @@ -189,6 +191,7 @@ OSV-Scanner reports one row per CVE per package. A single package with nine know Neither count is wrong. They reflect different output philosophies: OSV-Scanner gives you the raw advisory list; CVE Lite gives you the actionable package list. **OSV-Scanner output — hono package:** + ``` | https://osv.dev/GHSA-26pp-8wgv-hjvm | 5.3 | npm | hono | 4.12.9 | 4.12.12 | | https://osv.dev/GHSA-458j-xx4x-4375 | 4.3 | npm | hono | 4.12.9 | 4.12.14 | @@ -202,6 +205,7 @@ Neither count is wrong. They reflect different output philosophies: OSV-Scanner ``` **CVE Lite CLI output — same package:** + ``` MEDIUM hono@4.12.9 Transitive dependency @@ -258,10 +262,10 @@ Results can differ for several reasons: When scanning the NestJS monorepo example (`examples/nest`): -| Tool | Findings | Coverage | -|---|---|---| -| Snyk (root only, no `--all-projects`) | 6 issues, 13 paths | Root manifest only | -| CVE Lite CLI | 35 packages (3 critical, 10 high, 18 medium, 4 low) | Full lockfile | +| Tool | Findings | Coverage | +| ------------------------------------- | --------------------------------------------------- | ------------------ | +| Snyk (root only, no `--all-projects`) | 6 issues, 13 paths | Root manifest only | +| CVE Lite CLI | 35 packages (3 critical, 10 high, 18 medium, 4 low) | Full lockfile | Snyk itself warns: `50 manifests detected — use --all-projects to scan all of them at once`. With `--all-projects`, Snyk coverage improves significantly; without it, critical findings in nested workspaces are silently skipped. @@ -340,19 +344,19 @@ vulnerability that CVE Lite identifies and helps remediate. ### Feature comparison -| Capability | CVE Lite CLI | Socket CLI | -|---|:---:|:---:| -| Known CVE detection | ✅ | ✅ | -| Validated fix commands | ✅ | ❌ | -| Parent-aware transitive remediation | ✅ | ❌ | -| Offline advisory DB workflow | ✅ | ❌ | -| No account required | ✅ | ❌ | -| Local-first workflow | ✅ | ❌ | -| Malware detection | ❌ | ✅ | -| Typosquatting detection | ❌ | ✅ | -| Suspicious maintainer analysis | ❌ | ✅ | -| License risk detection | ❌ | ✅ | -| Supply-chain trust analysis | ❌ | ✅ | +| Capability | CVE Lite CLI | Socket CLI | +| ----------------------------------- | :----------: | :--------: | +| Known CVE detection | ✅ | ✅ | +| Validated fix commands | ✅ | ❌ | +| Parent-aware transitive remediation | ✅ | ❌ | +| Offline advisory DB workflow | ✅ | ❌ | +| No account required | ✅ | ❌ | +| Local-first workflow | ✅ | ❌ | +| Malware detection | ❌ | ✅ | +| Typosquatting detection | ❌ | ✅ | +| Suspicious maintainer analysis | ❌ | ✅ | +| License risk detection | ❌ | ✅ | +| Supply-chain trust analysis | ❌ | ✅ | ✅ = built-in strength · ⚠️ = partial or workflow-dependent · ❌ = not a core strength diff --git a/website/docs/corporate-proxy.md b/website/docs/corporate-proxy.md index 0bb12167..647cfa3d 100644 --- a/website/docs/corporate-proxy.md +++ b/website/docs/corporate-proxy.md @@ -65,16 +65,19 @@ The `--ca-cert` flag takes precedence over any saved config value for that run. Your IT or security team can provide the CA certificate. A few common ways to export it: **From the system keychain (macOS):** + 1. Open Keychain Access. 2. Find the root CA your proxy uses (usually named after your company or the proxy vendor). 3. Right-click and choose Export. Save as `.pem` or `.crt`. **From a browser (Chrome/Firefox):** + 1. Navigate to any HTTPS site. 2. Click the padlock icon and view the certificate chain. 3. Export the root CA certificate as PEM. **From an existing `.pfx` / `.p12` bundle (OpenSSL):** + ```bash openssl pkcs12 -in corporate.pfx -nokeys -cacerts -out corporate-ca.pem ``` diff --git a/website/docs/cyclonedx.md b/website/docs/cyclonedx.md index bb785c84..2f481964 100644 --- a/website/docs/cyclonedx.md +++ b/website/docs/cyclonedx.md @@ -20,13 +20,13 @@ The BOM includes **all packages** from the scanned lockfile as components — no Vulnerability data is attached for any packages with CVE findings: -| CycloneDX field | Value | -|---|---| -| `components[].purl` | `pkg:npm/@` | -| `vulnerabilities[].id` | CVE ID | +| CycloneDX field | Value | +| -------------------------------------- | ------------------------------------------------- | +| `components[].purl` | `pkg:npm/@` | +| `vulnerabilities[].id` | CVE ID | | `vulnerabilities[].ratings[].severity` | `critical`, `high`, `medium`, `low`, or `unknown` | -| `vulnerabilities[].affects[].ref` | Component purl | -| `vulnerabilities[].recommendation` | Runnable fix command or recommended action | +| `vulnerabilities[].affects[].ref` | Component purl | +| `vulnerabilities[].recommendation` | Runnable fix command or recommended action | One vulnerability entry is emitted per CVE ID. If multiple packages share a CVE, a single entry with multiple `affects` references is produced. diff --git a/website/docs/getting-started.md b/website/docs/getting-started.md index 9489acd2..bf327345 100644 --- a/website/docs/getting-started.md +++ b/website/docs/getting-started.md @@ -1,4 +1,3 @@ - # Getting Started CVE Lite CLI is a fast, local-first vulnerability scanner for JavaScript and TypeScript projects. @@ -41,11 +40,11 @@ That’s it. The CLI will: -* detect your lockfile (npm, pnpm, or Yarn) -* scan dependencies locally -* match vulnerabilities using OSV -* classify issues (direct vs transitive) -* suggest a fix plan when possible +- detect your lockfile (npm, pnpm, or Yarn) +- scan dependencies locally +- match vulnerabilities using OSV +- classify issues (direct vs transitive) +- suggest a fix plan when possible --- @@ -131,8 +130,8 @@ The output is intentionally structured to help you move from **detection → dec Each issue is clearly labeled: -* **Direct dependencies** → you can fix these immediately -* **Transitive dependencies** → require deeper inspection +- **Direct dependencies** → you can fix these immediately +- **Transitive dependencies** → require deeper inspection Example: @@ -152,9 +151,9 @@ This is the most important part of the output — and the main difference from m Instead of forcing you to: -* read logs -* search advisories -* manually figure out versions +- read logs +- search advisories +- manually figure out versions CVE Lite CLI generates **ready-to-run commands**: @@ -165,10 +164,10 @@ High severity fix commands These commands are: -* **validated against known vulnerable versions** -* **grouped by severity** -* **aligned with your package manager** -* **safe upgrade targets where available** +- **validated against known vulnerable versions** +- **grouped by severity** +- **aligned with your package manager** +- **safe upgrade targets where available** This means: @@ -189,8 +188,8 @@ Upgrade @angular/compiler → 19.2.20 This is useful when: -* you don’t have time to fix everything -* you want the biggest risk reduction first +- you don’t have time to fix everything +- you want the biggest risk reduction first --- @@ -217,14 +216,14 @@ Along with a final status: Most tools stop at **telling you what’s wrong**. CVE Lite CLI focuses on **getting you to a fix**. -* Clear prioritization -* Direct vs transitive visibility -* Actionable fix commands -* Minimal time from scan → fix +- Clear prioritization +- Direct vs transitive visibility +- Actionable fix commands +- Minimal time from scan → fix --- -## Before / After +## Before / After ```bash Before: @@ -262,22 +261,22 @@ After your first scan, these commands help you go deeper, automate checks, and i ### Generate a shareable report -``` bash +```bash cve-lite --report ``` Creates a local HTML report with: -* vulnerability summary -* direct vs transitive breakdown -* fix suggestions -* clean, readable layout for sharing +- vulnerability summary +- direct vs transitive breakdown +- fix suggestions +- clean, readable layout for sharing Useful for: -* sharing findings with your team -* attaching to GitHub issues -* documenting security reviews +- sharing findings with your team +- attaching to GitHub issues +- documenting security reviews [See more details](./html-report.md) @@ -288,11 +287,12 @@ Useful for: ```bash cve-lite --verbose ``` + Use this when you want: -* full vulnerability tables -* dependency paths (where available) -* deeper insight into transitive issues +- full vulnerability tables +- dependency paths (where available) +- deeper insight into transitive issues [See more details](./reading-output.md) @@ -303,17 +303,18 @@ Use this when you want: ``` cve-lite --fail-on high ``` + Common values: -* critical -* high -* medium -* low +- critical +- high +- medium +- low Useful for: -* CI pipelines -* enforcing security thresholds before release +- CI pipelines +- enforcing security thresholds before release [See more details](./workflow-integration.md) @@ -328,9 +329,9 @@ cve-lite --json Use this for: -* GitHub code scanning -* automated reporting -* integrating with other tools +- GitHub code scanning +- automated reporting +- integrating with other tools [See more details](./workflow-integration.md#offline-scan-with-json-output) @@ -358,8 +359,8 @@ Uses cached advisory data. Useful for: -* restricted environments -* faster repeated scans +- restricted environments +- faster repeated scans [See more details](./offline-advisory-db.md) @@ -369,6 +370,6 @@ CVE Lite CLI focuses on dependency vulnerability scanning. It does **not** currently provide: -* runtime exploitability analysis -* container or infrastructure scanning -* secrets detection \ No newline at end of file +- runtime exploitability analysis +- container or infrastructure scanning +- secrets detection diff --git a/website/docs/how-remediation-works.mdx b/website/docs/how-remediation-works.mdx index a0dd0915..7d6b2734 100644 --- a/website/docs/how-remediation-works.mdx +++ b/website/docs/how-remediation-works.mdx @@ -3,8 +3,8 @@ title: How Remediation Works description: How CVE Lite CLI determines fix commands for vulnerable dependencies — direct fixes, lockfile refreshes, parent upgrades, and best-effort suggestions. --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; # How Remediation Works @@ -45,12 +45,12 @@ The lockfile is a snapshot. It records the exact version of every package that w `package.json` declares version ranges, not exact versions: -| Range | Meaning | -|---|---| -| `^3.0.5` | Any version `>=3.0.5` and `<4.0.0` | -| `~3.0.5` | Any version `>=3.0.5` and `<3.1.0` | -| `>=3.0.5` | Any version `3.0.5` or higher | -| `3.0.5` | Exactly version `3.0.5` | +| Range | Meaning | +| --------- | ---------------------------------- | +| `^3.0.5` | Any version `>=3.0.5` and `<4.0.0` | +| `~3.0.5` | Any version `>=3.0.5` and `<3.1.0` | +| `>=3.0.5` | Any version `3.0.5` or higher | +| `3.0.5` | Exactly version `3.0.5` | The caret (`^`) is by far the most common. `"js-cookie": "^3.0.5"` means the package manager is allowed to install any `3.x.x` version at or above `3.0.5`. @@ -114,24 +114,16 @@ CVE Lite CLI validates the target version against OSV before suggesting it — i - ```bash - npm install lodash@4.18.0 - ``` + ```bash npm install lodash@4.18.0 ``` - ```bash - pnpm add lodash@4.18.0 - ``` + ```bash pnpm add lodash@4.18.0 ``` - ```bash - yarn add lodash@4.18.0 - ``` + ```bash yarn add lodash@4.18.0 ``` - ```bash - bun add lodash@4.18.0 - ``` + ```bash bun add lodash@4.18.0 ``` @@ -166,24 +158,16 @@ This is the preferred fix when it applies — it is the smallest possible change - ```bash - npm update js-cookie - ``` + ```bash npm update js-cookie ``` - ```bash - pnpm update --no-save js-cookie - ``` + ```bash pnpm update --no-save js-cookie ``` - ```bash - yarn upgrade js-cookie - ``` + ```bash yarn upgrade js-cookie ``` - ```bash - bun update js-cookie - ``` + ```bash bun update js-cookie ``` @@ -217,24 +201,16 @@ For pnpm, yarn, and bun, CVE Lite CLI uses a best-effort fallback rather than a - ```bash - npm install express@4.22.2 - ``` + ```bash npm install express@4.22.2 ``` - ```bash - pnpm add express@4.22.2 - ``` + ```bash pnpm add express@4.22.2 ``` - ```bash - yarn add express@4.22.2 - ``` + ```bash yarn add express@4.22.2 ``` - ```bash - bun add express@4.22.2 - ``` + ```bash bun add express@4.22.2 ``` @@ -271,24 +247,16 @@ Apply the suggested command for your package manager: - ```bash - npm install vercel@32.1.0 - ``` + ```bash npm install vercel@32.1.0 ``` - ```bash - pnpm add vercel@32.1.0 - ``` + ```bash pnpm add vercel@32.1.0 ``` - ```bash - yarn add vercel@32.1.0 - ``` + ```bash yarn add vercel@32.1.0 ``` - ```bash - bun add vercel@32.1.0 - ``` + ```bash bun add vercel@32.1.0 ``` @@ -322,24 +290,16 @@ flowchart TD - ```bash - npm install -w packages/app lodash@4.18.0 - ``` + ```bash npm install -w packages/app lodash@4.18.0 ``` - ```bash - pnpm add --filter ./packages/app lodash@4.18.0 - ``` + ```bash pnpm add --filter ./packages/app lodash@4.18.0 ``` - ```bash - yarn workspace app add lodash@4.18.0 - ``` + ```bash yarn workspace app add lodash@4.18.0 ``` - ```bash - bun add --filter packages/app lodash@4.18.0 - ``` + ```bash bun add --filter packages/app lodash@4.18.0 ``` @@ -371,24 +331,16 @@ CVE Lite CLI reports this as **one finding** — `lodash@4.17.20` — not two. T - ```bash - npm install lodash@4.18.0 - ``` + ```bash npm install lodash@4.18.0 ``` - ```bash - pnpm add lodash@4.18.0 - ``` + ```bash pnpm add lodash@4.18.0 ``` - ```bash - yarn add lodash@4.18.0 - ``` + ```bash yarn add lodash@4.18.0 ``` - ```bash - bun add lodash@4.18.0 - ``` + ```bash bun add lodash@4.18.0 ``` @@ -419,28 +371,16 @@ CVE Lite CLI reports these as **two separate findings** — `ws@8.5.0` and `ws@8 - ```bash - npm install express@4.21.0 - npm install socket.io@4.7.5 - ``` + ```bash npm install express@4.21.0 npm install socket.io@4.7.5 ``` - ```bash - pnpm add express@4.21.0 - pnpm add socket.io@4.7.5 - ``` + ```bash pnpm add express@4.21.0 pnpm add socket.io@4.7.5 ``` - ```bash - yarn add express@4.21.0 - yarn add socket.io@4.7.5 - ``` + ```bash yarn add express@4.21.0 yarn add socket.io@4.7.5 ``` - ```bash - bun add express@4.21.0 - bun add socket.io@4.7.5 - ``` + ```bash bun add express@4.21.0 bun add socket.io@4.7.5 ``` @@ -448,24 +388,16 @@ Or if both parents' declared ranges already cover a safe `ws` version, CVE Lite - ```bash - npm update ws - ``` + ```bash npm update ws ``` - ```bash - pnpm update --no-save ws - ``` + ```bash pnpm update --no-save ws ``` - ```bash - yarn upgrade ws - ``` + ```bash yarn upgrade ws ``` - ```bash - bun update ws - ``` + ```bash bun update ws ``` @@ -534,6 +466,7 @@ flowchart TD Skipped findings always appear in the output with an explicit reason — CVE Lite CLI never silently drops a vulnerability. Common skip reasons: + - No safe version exists for this package (all published versions are still vulnerable) - The dependency path could not be resolved from the lockfile - Running in offline mode and registry data is required for this fix type @@ -548,7 +481,7 @@ OSV includes a separate class of advisory for packages that were intentionally p CVE Lite CLI handles MAL- findings differently from CVE/GHSA findings because the nature of the risk is different: - **CVE/GHSA:** The vulnerability is in the package's code logic. `lodash@4.17.20` has a prototype pollution bug whether it came from the public npm registry or your private Artifactory mirror. The version is what matters. -- **MAL-:** The maliciousness *is* the artifact. A specific package published to the public npm registry at a specific version contained injected malicious code. If your private registry has a package with the same name and version, it is a completely different artifact - the MAL- advisory may not apply. +- **MAL-:** The maliciousness _is_ the artifact. A specific package published to the public npm registry at a specific version contained injected malicious code. If your private registry has a package with the same name and version, it is a completely different artifact - the MAL- advisory may not apply. ### Unverifiable (private source) @@ -559,6 +492,7 @@ When CVE Lite CLI detects that a package with a MAL- advisory resolved from a UR ``` This means: + - A MAL- advisory exists for a package with this name and version on the public npm registry - Your lockfile shows this package was installed from a different registry (private Artifactory, GitHub Packages, internal npm proxy, workspace link, etc.) - CVE Lite CLI cannot verify whether your artifact matches the malicious one @@ -598,24 +532,16 @@ Apply the suggested command for your package manager: - ```bash - npm install pkg@X.Y.Z - ``` + ```bash npm install pkg@X.Y.Z ``` - ```bash - pnpm add pkg@X.Y.Z - ``` + ```bash pnpm add pkg@X.Y.Z ``` - ```bash - yarn add pkg@X.Y.Z - ``` + ```bash yarn add pkg@X.Y.Z ``` - ```bash - bun add pkg@X.Y.Z - ``` + ```bash bun add pkg@X.Y.Z ``` diff --git a/website/docs/html-report.md b/website/docs/html-report.md index ca669f4a..28767d3a 100644 --- a/website/docs/html-report.md +++ b/website/docs/html-report.md @@ -25,10 +25,10 @@ cve-lite /path/to/project --report --no-open The report writes two files to the output directory: -| File | Description | -|---|---| -| `index.html` | Self-contained dashboard. Open in any browser — no server required. | -| `report.json` | Machine-readable scan data in JSON format. | +| File | Description | +| ------------- | ------------------------------------------------------------------- | +| `index.html` | Self-contained dashboard. Open in any browser — no server required. | +| `report.json` | Machine-readable scan data in JSON format. | Running `--report` to the same directory a second time overwrites both files. @@ -39,6 +39,7 @@ Running `--report` to the same directory a second time overwrites both files. **Suggested Fix Plan** mirrors the terminal output: copy-ready package manager commands for your direct dependencies, grouped by severity. Skipped entries (transitive or no fix available) are listed in a collapsible section. **Findings table** with interactive controls: + - Filter by severity or direct-only - Expandable rows showing vulnerability description, contextual risk summary, next action, dependency path, and recommended action @@ -47,10 +48,10 @@ Running `--report` to the same directory a second time overwrites both files. ## Options -| Flag | Default | Description | -|---|---|---| +| Flag | Default | Description | +| ---------------- | -------------- | --------------------------------------------------------------------- | | `--report [dir]` | `./cve-report` | Generate an HTML report in `[dir]`. Omit the path to use the default. | -| `--no-open` | — | Skip auto-opening the report in the browser after generation. | +| `--no-open` | — | Skip auto-opening the report in the browser after generation. | `--report` cannot be combined with `--json`. diff --git a/website/docs/parser-coverage.md b/website/docs/parser-coverage.md index 358a2314..03419b89 100644 --- a/website/docs/parser-coverage.md +++ b/website/docs/parser-coverage.md @@ -4,13 +4,13 @@ Supported lockfile formats, known limitations, and edge cases. ## Supported formats -| Lockfile | Package manager | Support level | Notes | -|---|---|---|---| -| `package-lock.json` | npm | Full | v1, v2, and v3 formats supported | -| `pnpm-lock.yaml` | pnpm | Full | v5, v6, and v9 formats supported | -| `yarn.lock` | Yarn | Full | Classic (v1) and Berry (v2/v3) formats supported | -| `bun.lock` | Bun | Full | JSONC format introduced in Bun v1.1.38+ | -| `package.json` | Any | Limited fallback | Only exact pinned direct dependencies (`"1.2.3"`, not `"^1.2.3"`) | +| Lockfile | Package manager | Support level | Notes | +| ------------------- | --------------- | ---------------- | ----------------------------------------------------------------- | +| `package-lock.json` | npm | Full | v1, v2, and v3 formats supported | +| `pnpm-lock.yaml` | pnpm | Full | v5, v6, and v9 formats supported | +| `yarn.lock` | Yarn | Full | Classic (v1) and Berry (v2/v3) formats supported | +| `bun.lock` | Bun | Full | JSONC format introduced in Bun v1.1.38+ | +| `package.json` | Any | Limited fallback | Only exact pinned direct dependencies (`"1.2.3"`, not `"^1.2.3"`) | --- diff --git a/website/docs/press.md b/website/docs/press.md index 316fea4c..7dff766b 100644 --- a/website/docs/press.md +++ b/website/docs/press.md @@ -19,7 +19,7 @@ Coverage from security publications, technology news outlets, and industry media **[Shift Left: How CVE-LITE CLI is Transforming Developer Security](https://sdtimes.com/security/shift-left-how-cve-lite-cli-is-transforming-developer-security/)** -*David Rubinstein, Editor-in-Chief — SD Times, June 2026* +_David Rubinstein, Editor-in-Chief — SD Times, June 2026_ > "The biggest problem is that the feedback is way too late." — Sonu Kapoor @@ -31,7 +31,7 @@ A dedicated feature by SD Times Editor-in-Chief David Rubinstein covering CVE Li **[OWASP Incubator Project Helps Developers Find and Fix Vulnerable Dependencies in Seconds](https://www.securityweek.com/owasp-incubator-project-helps-developers-find-and-fix-vulnerable-dependencies-in-seconds/)** -*Kevin Townsend — SecurityWeek* +_Kevin Townsend — SecurityWeek_ > "CVE Lite CLI is a free, open-source command line tool that scans your projects in seconds and tells you exactly which included packages contain a vulnerability." @@ -43,7 +43,7 @@ A dedicated feature covering CVE Lite CLI's developer-time scanning approach, OW **[As AI speeds coding, CVE Lite CLI keeps security deliberately AI-free](https://www.csoonline.com/article/4176701/as-ai-speeds-coding-cve-lite-cli-keeps-security-deliberately-ai-free.html)** -*Shweta Sharma — CSO Online* +_Shweta Sharma — CSO Online_ > "Developers should see dependency risks while they are still writing code, not hours later inside a failing CI pipeline." @@ -55,7 +55,7 @@ A dedicated feature covering the deliberate decision to keep CVE Lite CLI AI-fre **[Dependency remediation bolstered with CVE Lite CLI](https://www.reversinglabs.com/blog/cve-lite-cli)** -*John P. Mello Jr. — ReversingLabs* +_John P. Mello Jr. — ReversingLabs_ > "Most developers don't get actionable signals until CI fails, and by then, the cognitive cost of context-switching back to a dependency decision they made three hours ago is high." — Trey Ford, Chief Strategy and Trust Officer, Bugcrowd @@ -67,7 +67,7 @@ An in-depth industry analysis featuring independent commentary from five named s **[OWASP CVE Lite CLI – New Tool to Scan for Vulnerabilities in Your Projects](https://cybersecuritynews.com/owasp-cve-lite-cli-tool/)** -*Guru Baran — Cybersecurity News* +_Guru Baran — Cybersecurity News_ > "Runs at the moment just before a developer pushes code, producing a concrete remediation plan rather than just a list of vulnerability identifiers." @@ -79,7 +79,7 @@ Covers CVE Lite CLI's OWASP Lab Project status, remediation-focused output, lock **[OWASP CVE Lite CLI Scans JavaScript Dependencies for Vulns in Seconds](https://cipherssecurity.com/owasp-cve-lite-cli-dependency-scanner/)** -*Team Ciphers Security — June 2026* +_Team Ciphers Security — June 2026_ > "CVE Lite CLI fills a practical gap in the JavaScript developer security toolkit: a fast, local, fix-oriented dependency scanner that tells you not just what is vulnerable but precisely which package version to install to fix it." @@ -91,7 +91,7 @@ A structured editorial covering CVE Lite CLI's technical design, threat landscap **[Hottest cybersecurity open-source tools of the month: May 2026](https://www.helpnetsecurity.com/2026/05/28/hottest-cybersecurity-open-source-tools-of-the-month-may-2026/)** -*Help Net Security* +_Help Net Security_ > "CVE Lite CLI is an officially recognized OWASP Incubator Project that moves dependency vulnerability checks into the developer's terminal." @@ -103,7 +103,7 @@ Featured in Help Net Security's monthly roundup of standout open-source security **[CVE Lite CLI: Open-source dependency vulnerability scanner](https://www.helpnetsecurity.com/2026/05/20/cve-lite-cli-open-source-dependency-vulnerability-scanner/)** -*Mirko Zorz, Director of Content — Help Net Security* +_Mirko Zorz, Director of Content — Help Net Security_ > "CVE Lite CLI, now an officially recognized OWASP Incubator Project, moves that check to the developer's terminal." @@ -115,7 +115,7 @@ Covers the core premise of developer-time scanning, the direct vs transitive dis **[CVE Lite CLI: Open-source dependency vulnerability scanner](https://www.linuxtoday.com/blog/cve-lite-cli-open-source-dependency-vulnerability-scanner/)** -*James Patterson — Linux Today, June 2026* +_James Patterson — Linux Today, June 2026_ > "CVE Lite CLI, now an officially recognized OWASP Incubator Project, moves that check to the developer's terminal." @@ -127,7 +127,7 @@ Coverage in Linux Today, one of the longest-running Linux and open-source news p **[OWASP Adopts CVE Lite CLI to Boost Dependency Scanning](https://devops.com/owasp-adopts-cve-lite-cli-to-boost-dependency-scanning/)** -*DevOps.com* +_DevOps.com_ > "JavaScript and TypeScript developers can check for vulnerabilities themselves as they – or their agents – write their source code." @@ -139,7 +139,7 @@ Covers OWASP Lab Project adoption, local lockfile scanning against OSV, copy-and **[CVE Lite CLI repère les dépendances à risque](https://www.lemondeinformatique.fr/actualites/lire-cve-lite-cli-repere-les-dependances-a-risque-100270.html)** -*Le Monde Informatique (France)* +_Le Monde Informatique (France)_ > "Les développeurs devraient identifier les risques liés aux dépendances pendant qu'ils écrivent encore le code, et non plusieurs heures plus tard au sein d'un pipeline d'intégration continue défaillant." @@ -151,7 +151,7 @@ French-language coverage of the OWASP-backed tool, developer-time scanning, dire **[OWASP Incubator Project CVE Lite CLI Secures Developer Dependencies](https://techgig.com/news/software-devops/owasp-incubator-project-cve-lite-cli-secures-developer-dependencies/131546900)** -*TechGig (Economic Times)* +_TechGig (Economic Times)_ > "Sonu Kapoor, with 25 years of software development experience, highlights the frustration of developers flying blind regarding thousands of unknown dependencies." @@ -163,7 +163,7 @@ Indian tech news coverage from TechGig, part of the Economic Times ecosystem. Th **[CVE Lite CLI Joins OWASP as a Local-First Dependency Security Scanner](https://otontechnology.com/cve-lite-cli-owasp-dependency-vulnerability-scanner/)** -*Logan Pierce — Oton Technology* +_Logan Pierce — Oton Technology_ > "The useful part is helping developers understand which vulnerabilities are direct, which are transitive, which can be fixed now, and which require broader dependency decisions." @@ -175,7 +175,7 @@ Original reporting covering CVE Lite CLI's OWASP Lab Project status, lockfile sc **[CVE Lite CLI repère les dépendances à risque](https://csirt-universitaire.sn/publications/actualites/cve-lite-cli-repere-les-dependances-a-risque)** -*CSIRT Universitaire — Senegal* +_CSIRT Universitaire — Senegal_ > "Examines whether vulnerabilities are direct or transitive, validates upgrade targets, and recommends concrete remediation paths." @@ -187,7 +187,7 @@ French-language coverage published by the CSIRT Universitaire, the Computer Secu **[OWASP Incubator rozwija CVE Lite CLI do szybkiego wykrywania i usuwania podatnych zależności](https://securitybeztabu.pl/owasp-incubator-rozwija-cve-lite-cli-do-szybkiego-wykrywania-i-usuwania-podatnych-zaleznosci/)** -*Wojciech Ciemski — Security Bez Tabu (Poland)* +_Wojciech Ciemski — Security Bez Tabu (Poland)_ > "narzędzie może zidentyfikować nie tylko bezpośrednio zadeklarowane pakiety, ale również zależności pośrednie" (the tool identifies both directly declared and indirect dependencies) @@ -199,7 +199,7 @@ Polish-language original analysis covering CVE Lite CLI's OWASP Lab Project stat **[Lieferketten-Angriff: 5.500 GitHub-Repos in 6 Stunden kompromittiert](https://www.ad-hoc-news.de/wissenschaft/lieferketten-angriff-5-500-github-repos-in-6-stunden-kompromittiert/69418833)** -*ad-hoc-news (Germany)* +_ad-hoc-news (Germany)_ > "Die CVE Lite CLI, ein von OWASP unterstütztes Projekt, erlaubt Entwicklern, Abhängigkeits-Lockfiles lokal auf Schwachstellen zu scannen." @@ -211,7 +211,7 @@ German-language coverage in the context of supply-chain attacks, noting CVE Lite **[AIがコーディングを加速する中、CVE Lite CLIはセキュリティを意図的にAI無しに保つ](https://blackhatnews.tokyo/archives/104903)** -*TokyoBlackHatNews (Japan)* +_TokyoBlackHatNews (Japan)_ > "開発者はコードを書いている最中に依存関係のリスクを把握すべきであり、CIパイプラインが失敗してからでは遅すぎる。" @@ -223,7 +223,7 @@ Japanese-language coverage of developer-time lockfile scanning, OWASP incubator **[Silent Cyber Pressure Rises as OWASP CVE Lite CLI Emerges While European Medical Tech Faces Data Exposure Incident](https://undercodenews.com/silent-cyber-pressure-rises-as-owasp-cve-lite-cli-emerges-while-european-medical-tech-faces-data-exposure-incident-video/)** -*Undercode News* +_Undercode News_ > "This tool focuses on scanning package dependency lockfiles used by npm, pnpm, and Yarn, enabling developers to detect vulnerable dependencies directly on local machines within seconds." @@ -235,7 +235,7 @@ Coverage positioning CVE Lite CLI as defensive tool innovation at a moment when **[OWASP CVE Lite CLI Brings Local Vulnerability Scanning to JavaScript Projects](https://vpncentral.com/owasp-cve-lite-cli-brings-local-vulnerability-scanning-to-javascript-projects/)** -*Yash — VPN Central, June 2026* +_Yash — VPN Central, June 2026_ > "CVE Lite CLI helps developers understand which vulnerabilities are direct, which are transitive, which can be fixed locally — and gives them the exact command to do it." @@ -247,7 +247,7 @@ A dedicated feature covering CVE Lite CLI's OWASP Lab Project status, local lock **[Nueva herramienta de OWASP permite detectar y corregir dependencias vulnerables en entornos de desarrollo](https://blog.nivel4.com/ciberseguridad/nueva-herramienta-de-owasp-permite-detectar-y-corregir-dependencias-vulnerables-en-entornos-de-desarrollo)** -*patricionivel4 — Nivel4 Labs (Spanish), June 2026* +_patricionivel4 — Nivel4 Labs (Spanish), June 2026_ > "Analiza tus proyectos en segundos y te dice exactamente qué paquetes contienen una vulnerabilidad. Pero no solo identifica problemas — te dice cómo solucionarlos." — Sonu Kapoor @@ -269,7 +269,7 @@ A dedicated Brazilian Portuguese feature covering CVE Lite CLI's OWASP adoption, **[GitHub verrouille l'exécution des scripts d'installation pour npm](https://www.lemondeinformatique.fr/actualites/lire-github-verrouille-l-execution-des-scripts-d-installation-pour-npm-100441.html)** -*Evan Schuman (adapted by Dominique Filippone) — Le Monde Informatique (France), June 2026* +_Evan Schuman (adapted by Dominique Filippone) — Le Monde Informatique (France), June 2026_ > "Les gestionnaires de paquets passent de la confiance implicite à la confiance explicite." — Sonu Kapoor @@ -287,7 +287,7 @@ Hands-on evaluations by working security engineers and developers testing CVE Li **[I Ran OWASP CVE Lite CLI on a Real TypeScript App. Here's What It Actually Told Me.](https://blog.servarat.net/i-ran-owasp-cve-lite-cli-on-a-real-typescript-app-heres-what-it-actually-told-me/)** -*Mohamed Magdy — Servarat Blog* +_Mohamed Magdy — Servarat Blog_ > "Most dependency scanners are good at one thing: handing you a wall of CVE IDs and walking away." @@ -299,7 +299,7 @@ A hands-on practitioner review by a security engineer who tested CVE Lite CLI ag **[From Overwhelming CI Logs to Fix Plans: Rethinking TypeScript Dependency Scans](https://hackernoon.com/from-overwhelming-ci-logs-to-fix-plans-rethinking-typescript-dependency-scans)** -*Mert Satilmaz — HackerNoon* +_Mert Satilmaz — HackerNoon_ > "Instead of drowning developers in CVE IDs, CVE Lite CLI surfaces a concrete fix plan — the exact commands to run, grouped by severity and fix type." @@ -311,7 +311,7 @@ An independent technical review from an OWASP Project Lead and OSCP-certified se **[The postcss That Would Not Die, and How CVE Lite Ended My Override Grind](https://labs.hexaxia.tech/blog/hexops-cve-lite-integration/)** -*Aaron Lamb — Hexaxia Labs* +_Aaron Lamb — Hexaxia Labs_ > "Most tools tell you what's wrong. CVE Lite CLI tells you what to run." @@ -323,7 +323,7 @@ A hands-on engineering post covering a real integration of CVE Lite CLI into Hex **[CVE Lite CLI: The Dependency Scanner That Actually Tells You What to Run (Not Just What's Broken)](https://medium.com/@techlatest.net/cve-lite-cli-the-dependency-scanner-that-actually-tells-you-what-to-run-not-just-whats-broken-f6b518199981)** -*TechLatest.Net — Medium* +_TechLatest.Net — Medium_ > "Rather than blocking CI pipelines or overwhelming developers with CVE IDs, it emphasizes fast, local, developer-first scanning that fits into pre-push workflows." @@ -335,7 +335,7 @@ A dedicated hands-on review covering the full remediation cycle. The author crea **[AI Security Is Changing Fast — These 6 Open-Source Tools Prove It](https://medium.com/@techlatest.net/ai-security-is-changing-fast-these-6-open-source-tools-prove-it-5c5c9081cff7)** -*TechLatest.Net — Medium* +_TechLatest.Net — Medium_ > "Instead of 'This package is vulnerable,' it tells you 'Run this exact command to fix it.'" @@ -347,7 +347,7 @@ A roundup of six open-source security tools shaping the developer security space **[Shift Left, Further: OWASP CVE Lite CLI](http://tonibarth.bplaced.net/cve-lite-shift-left-security.html)** -*Toni Barth, Senior Quality Engineer, June 2026* +_Toni Barth, Senior Quality Engineer, June 2026_ > "CVE Lite CLI puts the first security evaluation directly after the implementation step — not after the merge, not after the deployment, but right there in your local workflow." @@ -359,7 +359,7 @@ A hands-on practitioner review from a Senior Quality Engineer who ran CVE Lite C **[GitHub finally pulls the plug on automatic install script execution for npm](https://www.csoonline.com/article/4183859/github-finally-pulls-the-plug-on-automatic-install-script-execution-for-npm-2.html)** -*Evan Schuman — CSO Online, June 2026* +_Evan Schuman — CSO Online, June 2026_ > "The essential point is that package managers are moving from implicit trust to explicit trust." — Sonu Kapoor @@ -371,7 +371,7 @@ Coverage of GitHub's decision to disable automatic npm install script execution. **[OWASP CVE Lite con I.A. Gemini](https://jroliva.net/2026/06/12/owasp-cve-lite-con-i-a-gemini/)** -*Juan Oliva — jroliva.net (Spanish), June 2026* +_Juan Oliva — jroliva.net (Spanish), June 2026_ A hands-on Spanish-language tutorial demonstrating CVE Lite CLI integrated with Google Gemini AI on Kali Linux, scanning OWASP Juice Shop. Shows the full AI agent workflow: installing the skill, running the scan, and generating a prioritised remediation plan without manual intervention. Demonstrates real-world adoption by a security practitioner in the Latin American / Spanish-speaking community. @@ -381,7 +381,7 @@ A hands-on Spanish-language tutorial demonstrating CVE Lite CLI integrated with **[Review of CVE Lite CLI](https://developmentcurated.com/testing-and-security/review-of-cve-lite-cli/)** -*Sebastian Raiffen, IT Security Consultant — Development Curated* +_Sebastian Raiffen, IT Security Consultant — Development Curated_ > "Rather than overwhelming teams with lengthy vulnerability lists, the tool focuses on fixable security issues that developers can address immediately." @@ -393,7 +393,7 @@ An independent practitioner review covering performance, lockfile-first design, **[How CVE Lite CLI Brings Dependency Security to Your Terminal](https://www.cyberinfos.in/cve-lite-cli-owasp-scanner-review/)** -*V Diwahar — CyberInfos* +_V Diwahar — CyberInfos_ > "CVE Lite CLI takes a different approach. It's a free, open-source scanner that just earned OWASP Incubator Project status, and it runs in the terminal before code gets pushed, not after." @@ -405,7 +405,7 @@ A practitioner-focused review covering CVE Lite CLI's local-first scanning model **[CVE Lite CLI: Revolutionizing Dependency Vulnerability Scanning for Secure Coding](https://codunix.com/cve-lite-cli-revolutionizing-dependency-vulnerability-scanning-for-secure-coding/)** -*Majdi Draouil — Codunix, June 2026* +_Majdi Draouil — Codunix, June 2026_ > "CVE Lite CLI offers a straightforward and efficient way to ensure the security and integrity of your projects." diff --git a/website/docs/ratcheting.md b/website/docs/ratcheting.md index 7d1da7e2..9d1bbd36 100644 --- a/website/docs/ratcheting.md +++ b/website/docs/ratcheting.md @@ -83,6 +83,7 @@ To run a full scan without suppression, delete or rename `.cve-lite/baseline.jso A finding is suppressed if every advisory ID on that `name@version` combination was present when the baseline was saved. If a package gains a new advisory ID not in the baseline, that finding surfaces as new - even if the package version itself was already baselined. This means: + - Same package, same version, same advisory - suppressed - Same package, same version, new advisory - surfaces as new - New package not in baseline at all - surfaces as new diff --git a/website/docs/remediation-strategy.mdx b/website/docs/remediation-strategy.mdx index a6d0893b..dce0f03c 100644 --- a/website/docs/remediation-strategy.mdx +++ b/website/docs/remediation-strategy.mdx @@ -3,8 +3,8 @@ title: Remediation Strategy description: Technical detail on how CVE Lite CLI determines fix commands — direct fixes, within-range refreshes, parent upgrades, and confidence levels. --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; # Remediation Strategy @@ -47,24 +47,16 @@ A direct dependency can usually be changed by updating the package itself: - ```bash - npm install vulnerable-package@fixed-version - ``` + ```bash npm install vulnerable-package@fixed-version ``` - ```bash - pnpm add vulnerable-package@fixed-version - ``` + ```bash pnpm add vulnerable-package@fixed-version ``` - ```bash - yarn add vulnerable-package@fixed-version - ``` + ```bash yarn add vulnerable-package@fixed-version ``` - ```bash - bun add vulnerable-package@fixed-version - ``` + ```bash bun add vulnerable-package@fixed-version ``` @@ -97,24 +89,16 @@ When those checks pass, the CLI emits a package-manager-native command: - ```bash - npm install minimist@1.2.8 - ``` + ```bash npm install minimist@1.2.8 ``` - ```bash - pnpm add minimist@1.2.8 - ``` + ```bash pnpm add minimist@1.2.8 ``` - ```bash - yarn add minimist@1.2.8 - ``` + ```bash yarn add minimist@1.2.8 ``` - ```bash - bun add minimist@1.2.8 - ``` + ```bash bun add minimist@1.2.8 ``` @@ -122,24 +106,16 @@ In workspace monorepos, the command includes the appropriate workspace flag so t - ```bash - npm install -w packages/api minimist@1.2.8 - ``` + ```bash npm install -w packages/api minimist@1.2.8 ``` - ```bash - pnpm add --filter ./packages/api minimist@1.2.8 - ``` + ```bash pnpm add --filter ./packages/api minimist@1.2.8 ``` - ```bash - yarn workspace api-package add minimist@1.2.8 - ``` + ```bash yarn workspace api-package add minimist@1.2.8 ``` - ```bash - bun add --filter packages/api minimist@1.2.8 - ``` + ```bash bun add --filter packages/api minimist@1.2.8 ``` @@ -188,24 +164,16 @@ flowchart TD - ```bash - npm update mocha - ``` + ```bash npm update mocha ``` - ```bash - pnpm update --no-save mocha - ``` + ```bash pnpm update --no-save mocha ``` - ```bash - yarn upgrade mocha - ``` + ```bash yarn upgrade mocha ``` - ```bash - bun update mocha - ``` + ```bash bun update mocha ``` @@ -225,24 +193,16 @@ It looks for a parent version that: - ```bash - npm install parent-package@newer-version - ``` + ```bash npm install parent-package@newer-version ``` - ```bash - pnpm add parent-package@newer-version - ``` + ```bash pnpm add parent-package@newer-version ``` - ```bash - yarn add parent-package@newer-version - ``` + ```bash yarn add parent-package@newer-version ``` - ```bash - bun add parent-package@newer-version - ``` + ```bash bun add parent-package@newer-version ``` @@ -327,24 +287,16 @@ Running this installs `vulnerable-child` as a **direct** dependency of your proj - ```bash - npm install vulnerable-child@fixed-version - ``` + ```bash npm install vulnerable-child@fixed-version ``` - ```bash - pnpm add vulnerable-child@fixed-version - ``` + ```bash pnpm add vulnerable-child@fixed-version ``` - ```bash - yarn add vulnerable-child@fixed-version - ``` + ```bash yarn add vulnerable-child@fixed-version ``` - ```bash - bun add vulnerable-child@fixed-version - ``` + ```bash bun add vulnerable-child@fixed-version ``` diff --git a/website/docs/sarif.md b/website/docs/sarif.md index fc3867e6..085bf4b4 100644 --- a/website/docs/sarif.md +++ b/website/docs/sarif.md @@ -33,7 +33,7 @@ jobs: scan: runs-on: ubuntu-latest permissions: - security-events: write # required for upload-sarif + security-events: write # required for upload-sarif steps: - uses: actions/checkout@v4 @@ -59,13 +59,13 @@ Use `if: always()` on the upload step so findings are uploaded even when `--fail Each CVE found produces one SARIF result. A package with multiple CVEs produces one result per CVE, allowing per-CVE review and dismissal in GitHub Code Scanning. -| SARIF field | Value | -|---|---| -| `ruleId` | CVE ID (e.g. `CVE-2021-44228`) | -| `level` | `error` (critical/high), `warning` (medium), `note` (low/unknown) | -| `message` | Package, version, severity, and recommended action | -| `locations` | Lockfile path relative to repo root | -| `fixes` | Exact install command when one is available | +| SARIF field | Value | +| ----------- | ----------------------------------------------------------------- | +| `ruleId` | CVE ID (e.g. `CVE-2021-44228`) | +| `level` | `error` (critical/high), `warning` (medium), `note` (low/unknown) | +| `message` | Package, version, severity, and recommended action | +| `locations` | Lockfile path relative to repo root | +| `fixes` | Exact install command when one is available | ## `--fail-on` and exit codes diff --git a/website/docs/security-assurance-case.md b/website/docs/security-assurance-case.md index 789b1393..99cd137c 100644 --- a/website/docs/security-assurance-case.md +++ b/website/docs/security-assurance-case.md @@ -31,13 +31,13 @@ The scope of these claims is the CLI itself. They do not extend to the security ### Threat actors -| Actor | Goal | Reachable through | -| --- | --- | --- | -| Malicious lockfile author | Trick the scanner into producing wrong results, crashing, or executing attacker-controlled code | Crafted `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, or `package.json` | -| Compromised or impersonated OSV endpoint | Inject false advisories or suppress real ones, or push attacker-controlled data into the local cache | OSV API (`api.osv.dev`), OSV bulk export (`storage.googleapis.com/osv-vulnerabilities/npm/all.zip`) | -| Compromised npm registry response | Inject false fix-version data into remediation suggestions | `registry.npmjs.org` packument fetches during fix-target validation | -| Supply-chain attacker on the build/release pipeline | Replace a release artifact with a malicious one between build and download | GitHub Actions runners, GitHub release uploads, npm registry | -| Local attacker on the developer's machine | Read or modify the local advisory database, OSV cache, or signing keys | Filesystem access | +| Actor | Goal | Reachable through | +| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| Malicious lockfile author | Trick the scanner into producing wrong results, crashing, or executing attacker-controlled code | Crafted `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, or `package.json` | +| Compromised or impersonated OSV endpoint | Inject false advisories or suppress real ones, or push attacker-controlled data into the local cache | OSV API (`api.osv.dev`), OSV bulk export (`storage.googleapis.com/osv-vulnerabilities/npm/all.zip`) | +| Compromised npm registry response | Inject false fix-version data into remediation suggestions | `registry.npmjs.org` packument fetches during fix-target validation | +| Supply-chain attacker on the build/release pipeline | Replace a release artifact with a malicious one between build and download | GitHub Actions runners, GitHub release uploads, npm registry | +| Local attacker on the developer's machine | Read or modify the local advisory database, OSV cache, or signing keys | Filesystem access | ### Assets @@ -81,17 +81,17 @@ The scope of these claims is the CLI itself. They do not extend to the security **Boundaries and what crosses them:** -| Boundary | Direction | Data | Trust treatment | -| --- | --- | --- | --- | -| User → CLI argument parser | In | Flag names, paths, severity strings | Allowlist on flags; unknown flags rejected (`src/cli/args.ts`). | -| Lockfile / `package.json` → parser | In | JSON, YAML, text | Defensive parse with `try`/`catch`; missing fields handled via optional chaining; version strings filtered through `looksLikeVersion`. | -| OSV API / OSV bulk export → ingest | In | JSON advisory records | Records without an `id` are skipped; records with a `withdrawn` timestamp are skipped at ingest; ranges with non-SEMVER `type` are skipped. | -| npm registry → fix-target validator | In | Packument JSON | Version keys filtered through `looksLikeVersion` and `isPreReleaseVersion` before any comparison. | -| Local advisory SQLite DB → query | In | Stored advisory rows | Parameterized queries via `better-sqlite3`; no string concatenation. | -| GitHub Actions OIDC → Sigstore | Out | OIDC token | Ephemeral; used once per build to mint a Sigstore signing certificate. No long-lived key. | -| Project lead's GPG key → release tag | Out | Signature | Private key remains on the project lead's local machine; only the public key is published. | -| CLI → user terminal | Out | Findings, fix commands | Output is plain text; the CLI does not execute fix commands itself. | -| CLI → user's source tree | Out | None (default), `npm install` invocation in opt-in `--fix` mode | `--fix` is explicit, scoped to the package manager's own command, and rescans afterward. | +| Boundary | Direction | Data | Trust treatment | +| ------------------------------------ | --------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| User → CLI argument parser | In | Flag names, paths, severity strings | Allowlist on flags; unknown flags rejected (`src/cli/args.ts`). | +| Lockfile / `package.json` → parser | In | JSON, YAML, text | Defensive parse with `try`/`catch`; missing fields handled via optional chaining; version strings filtered through `looksLikeVersion`. | +| OSV API / OSV bulk export → ingest | In | JSON advisory records | Records without an `id` are skipped; records with a `withdrawn` timestamp are skipped at ingest; ranges with non-SEMVER `type` are skipped. | +| npm registry → fix-target validator | In | Packument JSON | Version keys filtered through `looksLikeVersion` and `isPreReleaseVersion` before any comparison. | +| Local advisory SQLite DB → query | In | Stored advisory rows | Parameterized queries via `better-sqlite3`; no string concatenation. | +| GitHub Actions OIDC → Sigstore | Out | OIDC token | Ephemeral; used once per build to mint a Sigstore signing certificate. No long-lived key. | +| Project lead's GPG key → release tag | Out | Signature | Private key remains on the project lead's local machine; only the public key is published. | +| CLI → user terminal | Out | Findings, fix commands | Output is plain text; the CLI does not execute fix commands itself. | +| CLI → user's source tree | Out | None (default), `npm install` invocation in opt-in `--fix` mode | `--fix` is explicit, scoped to the package manager's own command, and rescans afterward. | ## Secure design principles applied @@ -110,18 +110,18 @@ The project applies the [Saltzer and Schroeder](https://en.wikipedia.org/wiki/Sa Mapped against the [OWASP Top 10 (2021)](https://owasp.org/Top10/) — the categories most projects use as a checklist: -| OWASP category | Status | How | -| --- | --- | --- | -| A01: Broken Access Control | Not applicable | The CLI has no authentication or authorization surface. There are no users to authorize. | -| A02: Cryptographic Failures | Countered | No custom cryptography. Release signing uses Sigstore (industry standard) and GPG (for tags). No secrets are stored in source or in CI configuration. The npm registry's automatic signature provides a third independent verification path. | -| A03: Injection | Countered | All SQLite access uses parameterized prepared statements via `better-sqlite3`. The CLI does not construct or execute shell commands from user input — fix commands are printed to stdout for the user to copy. There is no use of `eval`, the `Function` constructor, or template-string-built queries. | -| A04: Insecure Design | Countered | Threat model and trust boundaries are documented (this page). Inputs are allowlisted where they have format restrictions. Defaults fail safe. | -| A05: Security Misconfiguration | Countered | Minimal configuration surface — most behavior is determined by the lockfile and advisory data, not by user knobs. The default severity threshold is conservative. CI workflows pin action versions and use the minimum required permissions. | -| A06: Vulnerable and Outdated Components | Countered | The project scans its own lockfile in CI on every push (the "self-scan" workflow). The runtime dependency footprint is intentionally small. CodeQL static analysis runs on every push and pull request. | -| A07: Identification and Authentication Failures | Not applicable | The CLI has no authentication surface. | -| A08: Software and Data Integrity Failures | Countered | Release tarballs are signed via Sigstore Artifact Attestations. Git tags are GPG-signed by the project lead. Withdrawn OSV advisories are filtered at ingest. Sync replaces the advisory database atomically rather than partially updating it. | -| A09: Security Logging and Monitoring Failures | Partially countered | CodeQL alerts on every push, the self-scan workflow flags new advisories in the project's own dependencies on every push, and the GitHub release workflow emits build provenance. The project does not centrally log scan invocations because none happen on project-controlled infrastructure — scans run on each user's local machine. | -| A10: Server-Side Request Forgery | Countered | The CLI only makes outbound HTTP requests to fixed, hardcoded endpoints: `api.osv.dev`, `storage.googleapis.com/osv-vulnerabilities/...`, and `registry.npmjs.org`. No user-controlled URLs are ever used as request targets. | +| OWASP category | Status | How | +| ----------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| A01: Broken Access Control | Not applicable | The CLI has no authentication or authorization surface. There are no users to authorize. | +| A02: Cryptographic Failures | Countered | No custom cryptography. Release signing uses Sigstore (industry standard) and GPG (for tags). No secrets are stored in source or in CI configuration. The npm registry's automatic signature provides a third independent verification path. | +| A03: Injection | Countered | All SQLite access uses parameterized prepared statements via `better-sqlite3`. The CLI does not construct or execute shell commands from user input — fix commands are printed to stdout for the user to copy. There is no use of `eval`, the `Function` constructor, or template-string-built queries. | +| A04: Insecure Design | Countered | Threat model and trust boundaries are documented (this page). Inputs are allowlisted where they have format restrictions. Defaults fail safe. | +| A05: Security Misconfiguration | Countered | Minimal configuration surface — most behavior is determined by the lockfile and advisory data, not by user knobs. The default severity threshold is conservative. CI workflows pin action versions and use the minimum required permissions. | +| A06: Vulnerable and Outdated Components | Countered | The project scans its own lockfile in CI on every push (the "self-scan" workflow). The runtime dependency footprint is intentionally small. CodeQL static analysis runs on every push and pull request. | +| A07: Identification and Authentication Failures | Not applicable | The CLI has no authentication surface. | +| A08: Software and Data Integrity Failures | Countered | Release tarballs are signed via Sigstore Artifact Attestations. Git tags are GPG-signed by the project lead. Withdrawn OSV advisories are filtered at ingest. Sync replaces the advisory database atomically rather than partially updating it. | +| A09: Security Logging and Monitoring Failures | Partially countered | CodeQL alerts on every push, the self-scan workflow flags new advisories in the project's own dependencies on every push, and the GitHub release workflow emits build provenance. The project does not centrally log scan invocations because none happen on project-controlled infrastructure — scans run on each user's local machine. | +| A10: Server-Side Request Forgery | Countered | The CLI only makes outbound HTTP requests to fixed, hardcoded endpoints: `api.osv.dev`, `storage.googleapis.com/osv-vulnerabilities/...`, and `registry.npmjs.org`. No user-controlled URLs are ever used as request targets. | ## Limitations and explicit non-goals diff --git a/website/docs/troubleshooting.md b/website/docs/troubleshooting.md index a625957f..30da64f7 100644 --- a/website/docs/troubleshooting.md +++ b/website/docs/troubleshooting.md @@ -22,6 +22,7 @@ Common issues and how to resolve them. **Cause:** The tool looks for `package-lock.json`, `pnpm-lock.yaml`, or `yarn.lock` in the project directory. If none is present, it falls back to exact pinned versions in `package.json`. **Fix:** + - Make sure you are pointing the scan at the root of the project, not a subdirectory. - Run `npm install`, `pnpm install`, or `yarn install` to generate a lockfile before scanning. - If your project intentionally has no lockfile, the `package.json` fallback only covers exact pinned direct dependencies. @@ -72,6 +73,7 @@ See [Corporate SSL Proxy](./corporate-proxy.md) for the full setup and export in **Cause:** The first sync downloads the full OSV npm advisory dump (~200K+ records). Subsequent syncs are faster because of bulk SQLite ingestion optimizations. **Fix:** + - Let the first sync complete. On a typical machine it runs in under 10 seconds after the download. - If the download itself is slow, check your network connection or consider running the sync during off-peak hours and caching the result. @@ -96,6 +98,7 @@ See [Corporate SSL Proxy](./corporate-proxy.md) for the full setup and export in **Cause:** `--fix` only applies fixes for direct dependencies with a validated lowest known non-vulnerable version. It does not auto-apply transitive overrides. **Fix:** + - Check the scan output for the `skipped` section to see why a finding was not auto-fixed. - For transitive issues, follow the parent-upgrade guidance in the verbose output instead. - Run `cve-lite /path/to/project --verbose` to see the full fix plan including manual steps. diff --git a/website/docs/workflow-integration.md b/website/docs/workflow-integration.md index 5fc8eb84..72fddf41 100644 --- a/website/docs/workflow-integration.md +++ b/website/docs/workflow-integration.md @@ -118,7 +118,7 @@ jobs: scan: runs-on: ubuntu-latest permissions: - security-events: write # required for upload-sarif + security-events: write # required for upload-sarif steps: - uses: actions/checkout@v6 - uses: OWASP/cve-lite-cli@v1 diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index ffcc1bb5..4d005201 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -1,202 +1,202 @@ -import {themes as prismThemes} from 'prism-react-renderer'; -import type {Config} from '@docusaurus/types'; -import type * as Preset from '@docusaurus/preset-classic'; +import { themes as prismThemes } from "prism-react-renderer"; +import type { Config } from "@docusaurus/types"; +import type * as Preset from "@docusaurus/preset-classic"; -const latestVersion = 'v1.24.0'; +const latestVersion = "v1.24.0"; const config: Config = { - title: 'CVE Lite CLI', - tagline: 'Scan. Understand. Fix.', - favicon: 'img/favicon.png', + title: "CVE Lite CLI", + tagline: "Scan. Understand. Fix.", + favicon: "img/favicon.png", future: { v4: true, }, - url: 'https://owasp.org', - baseUrl: '/cve-lite-cli/', - organizationName: 'OWASP', - projectName: 'cve-lite-cli', + url: "https://owasp.org", + baseUrl: "/cve-lite-cli/", + organizationName: "OWASP", + projectName: "cve-lite-cli", trailingSlash: false, - onBrokenLinks: 'throw', + onBrokenLinks: "throw", markdown: { mermaid: true, hooks: { - onBrokenMarkdownLinks: 'warn', + onBrokenMarkdownLinks: "warn", }, }, - themes: ['@docusaurus/theme-mermaid'], + themes: ["@docusaurus/theme-mermaid"], i18n: { - defaultLocale: 'en', - locales: ['en'], + defaultLocale: "en", + locales: ["en"], }, presets: [ [ - 'classic', + "classic", { docs: { - sidebarPath: './sidebars.ts', - editUrl: 'https://github.com/OWASP/cve-lite-cli/tree/main/website/', + sidebarPath: "./sidebars.ts", + editUrl: "https://github.com/OWASP/cve-lite-cli/tree/main/website/", }, blog: false, theme: { - customCss: './src/css/custom.css', + customCss: "./src/css/custom.css", }, } satisfies Preset.Options, ], ], plugins: [ [ - '@easyops-cn/docusaurus-search-local', + "@easyops-cn/docusaurus-search-local", { hashed: true, indexDocs: true, indexBlog: false, indexPages: true, - docsRouteBasePath: 'docs', + docsRouteBasePath: "docs", searchBarShortcut: true, - searchBarShortcutKeymap: 'mod+k', - searchBarPosition: 'right', + searchBarShortcutKeymap: "mod+k", + searchBarPosition: "right", }, ], ], themeConfig: { - image: 'img/logos-combined.svg', + image: "img/logos-combined.svg", colorMode: { - defaultMode: 'dark', + defaultMode: "dark", disableSwitch: true, respectPrefersColorScheme: false, }, metadata: [ { - name: 'description', + name: "description", content: - 'Free JS/TS dependency vulnerability scanner. Scan npm, pnpm, Yarn, and Bun lockfiles locally, get parent-aware remediation guidance, and run offline.', + "Free JS/TS dependency vulnerability scanner. Scan npm, pnpm, Yarn, and Bun lockfiles locally, get parent-aware remediation guidance, and run offline.", }, { - name: 'keywords', + name: "keywords", content: - 'JavaScript vulnerability scanner, TypeScript CVE scanner, npm audit alternative, dependency vulnerability scanner, lockfile scanner, OWASP, CVE scanner', + "JavaScript vulnerability scanner, TypeScript CVE scanner, npm audit alternative, dependency vulnerability scanner, lockfile scanner, OWASP, CVE scanner", }, ], navbar: { logo: { - alt: 'CVE Lite CLI', - src: 'img/logo-with-title-removebg-preview.png', + alt: "CVE Lite CLI", + src: "img/logo-with-title-removebg-preview.png", }, items: [ { - to: '/docs', - position: 'left', - label: 'Docs', - activeBaseRegex: '^/docs/?$', + to: "/docs", + position: "left", + label: "Docs", + activeBaseRegex: "^/docs/?$", }, { - to: '/docs/getting-started', - label: 'Getting Started', - position: 'left', - activeBaseRegex: '^/docs/getting-started/?$', + to: "/docs/getting-started", + label: "Getting Started", + position: "left", + activeBaseRegex: "^/docs/getting-started/?$", }, { - to: '/docs/remediation-strategy', - label: 'Remediation', - position: 'left', - activeBaseRegex: '^/docs/remediation-strategy/?$', + to: "/docs/remediation-strategy", + label: "Remediation", + position: "left", + activeBaseRegex: "^/docs/remediation-strategy/?$", }, { - to: '/docs/comparison', - label: 'Compare', - position: 'left', - activeBaseRegex: '^/docs/comparison/?$', + to: "/docs/comparison", + label: "Compare", + position: "left", + activeBaseRegex: "^/docs/comparison/?$", }, { - to: '/docs/case-studies', - label: 'Case Studies', - position: 'left', - activeBaseRegex: '^/docs/case-studies', + to: "/docs/case-studies", + label: "Case Studies", + position: "left", + activeBaseRegex: "^/docs/case-studies", }, { - href: 'https://owasp.org/cve-lite-cli', + href: "https://owasp.org/cve-lite-cli", html: 'An OWASP Foundation Project', - 'aria-label': 'CVE Lite CLI is an OWASP Foundation Project', - className: 'navbar-owasp-link', - position: 'right', + "aria-label": "CVE Lite CLI is an OWASP Foundation Project", + className: "navbar-owasp-link", + position: "right", }, { - href: 'https://www.npmjs.com/package/cve-lite-cli', + href: "https://www.npmjs.com/package/cve-lite-cli", html: ``, - 'aria-label': `View CVE Lite CLI ${latestVersion} on npm`, - className: 'navbar-icon-link', - position: 'right', + "aria-label": `View CVE Lite CLI ${latestVersion} on npm`, + className: "navbar-icon-link", + position: "right", }, { - href: 'https://github.com/OWASP/cve-lite-cli', + href: "https://github.com/OWASP/cve-lite-cli", html: 'GitHub', - 'aria-label': 'View CVE Lite CLI on GitHub', - className: 'navbar-github-link', - position: 'right', + "aria-label": "View CVE Lite CLI on GitHub", + className: "navbar-github-link", + position: "right", }, ], }, footer: { - style: 'dark', + style: "dark", links: [ { - title: 'Guides', + title: "Guides", items: [ { - label: 'Remediation strategy', - to: '/docs/remediation-strategy', + label: "Remediation strategy", + to: "/docs/remediation-strategy", }, { - label: 'HTML reports', - to: '/docs/html-report', + label: "HTML reports", + to: "/docs/html-report", }, { - label: 'Offline advisory DB', - to: '/docs/offline-advisory-db', + label: "Offline advisory DB", + to: "/docs/offline-advisory-db", }, ], }, { - title: 'Project', + title: "Project", items: [ { - label: 'GitHub', - href: 'https://github.com/OWASP/cve-lite-cli', + label: "GitHub", + href: "https://github.com/OWASP/cve-lite-cli", }, { - label: 'npm', - href: 'https://www.npmjs.com/package/cve-lite-cli', + label: "npm", + href: "https://www.npmjs.com/package/cve-lite-cli", }, { - label: 'GitHub Action', - href: 'https://github.com/marketplace/actions/cve-lite-cli', + label: "GitHub Action", + href: "https://github.com/marketplace/actions/cve-lite-cli", }, ], }, { - title: 'Community', + title: "Community", items: [ { - label: 'Open an issue', - href: 'https://github.com/OWASP/cve-lite-cli/issues', + label: "Open an issue", + href: "https://github.com/OWASP/cve-lite-cli/issues", }, { - label: 'Contributing', - href: 'https://github.com/OWASP/cve-lite-cli/blob/main/src/docs/CONTRIBUTING.md', + label: "Contributing", + href: "https://github.com/OWASP/cve-lite-cli/blob/main/src/docs/CONTRIBUTING.md", }, { - label: 'Security', - href: 'https://github.com/OWASP/cve-lite-cli/blob/main/src/docs/SECURITY.md', + label: "Security", + href: "https://github.com/OWASP/cve-lite-cli/blob/main/src/docs/SECURITY.md", }, { - label: 'Press', - to: '/docs/press', + label: "Press", + to: "/docs/press", }, ], }, diff --git a/website/package-lock.json b/website/package-lock.json index 26554f68..597b84d0 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -31,15 +31,15 @@ } }, "node_modules/@algolia/abtesting": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.18.0.tgz", - "integrity": "sha512-8siuLG+FIns1AjZ/g2SDVwHz9S+ObacDQISEJvS8XsNei1zl3FXqfqQrBpmrG7ACWCyesXHbicMJtvRbg00FEw==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.21.0.tgz", + "integrity": "sha512-kGvHfBa9oQCvZh0YXeguSToBD9GNJ+gzUZQ9KPTg+KSsM36obYcsKPoX0NnlJtPflHXu7RkMaIi44xs9meR6Zw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" @@ -78,99 +78,99 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.52.0.tgz", - "integrity": "sha512-wtwPgyPmO7b7sQPVgoK29c1VpfS08DnnJCmxX/oU1pV2DlMRJCzQcLN7JSloYpodyKHwM8+9wOzlAM0co3TDmA==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.55.0.tgz", + "integrity": "sha512-Zt2GjIm7vsaf7K23tk5JmtcVNc38G9p0C2L2Lrm06miyLE/NL2etHtHInvuLc1DjxTp7Y2nId4X/tzwo372K8Q==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.52.0.tgz", - "integrity": "sha512-9KY36bRl4AH7RjqSeDDOKnjsz4IxQFBEOB8/fWmEbdQe+Isbs5jGzVJu9NEPQ1Tgwxlf8Uf07Swj3jZyMNUZ2g==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.55.0.tgz", + "integrity": "sha512-7BueMuWYg/KBA2EX9zsQ+3OAleEyrJcB+SV5Al/9pLjMQq5mXB/8M5HaUPqZwN812g5kLzj9j43VThlZgWq0hg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.52.0.tgz", - "integrity": "sha512-3a/qM3dzJqqfTx7Yrw7uGQ98I3Q0rDfb4Vkv0wEzko96l7YQMxfBVz/VbLq2N+c59GweYv6Vhp8mPeqnWJSITw==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.55.0.tgz", + "integrity": "sha512-pJZIyhvUrs+B7c5Lw0iP5yP/NsqJMda7pKRYbfG4KtfGIVSMcAalZhdqL5UX8Z9DOC4KxO9tKV5RDeVjZU0VfQ==", "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-insights": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.52.0.tgz", - "integrity": "sha512-Rki7ACbMcvbQW0BuM84x9dkGHY47ABmv4jU6tYssat2k02p3mIUms2YOLUAMeknhmnFsj6lb6ZzOXdMWMyc1sA==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.55.0.tgz", + "integrity": "sha512-RydkKDhx0GWTYuw0ndTXHGM8hD8hgwftKE65FfnJZb5bPc9CevOqv3qNPUQiviAwkqT9hQNH31uDGeV3yZkgfA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.52.0.tgz", - "integrity": "sha512-96s4Uzc3kk+/f4jJXIVVGWP5XlngOGNQ1x6hW9AT59pOixHlOs5tqJg+ZUS/GQ6h/iYP0ceQcmxDQeLyCLTaDQ==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.55.0.tgz", + "integrity": "sha512-XiS7gdFq/COWiwdWXZ8+RHuewfEo03TkGESk44zU8zTc/Z6R8fm4DNmV52swJKkeB2N9iC7NKpgpM22OOkcgTw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.52.0.tgz", - "integrity": "sha512-lqeycNpSPe5Qa0OUWpejVvYQjQWV5nQuLT0a4aq7XzRAvCxprV/6Lf841EygdD2nrFnuS58ok7Au1uOtXzpnkg==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.55.0.tgz", + "integrity": "sha512-LBEJ/q+hn1nJ0aYg5IcWgLNCPjWHTahWmpHNx1qUZMho+9CyWM6LaEnhac45UHjQm/j0m374HP685VrpL133lA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.52.0.tgz", - "integrity": "sha512-ly1wETVGRo30cx61O7fetESN+ElL9c9K+bD/AVgnT1ar4c6v+/Yqjrhdtu6Fm4D0s4NZP081Isf6tunH1wUXHg==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.55.0.tgz", + "integrity": "sha512-2/9jUXKH4IcdU5qxH6cbDH46ZBe46G7xr+MrcHwgEXZcUfdAvUgLSH53MAWuMgxvw0G5yoqiWMifHc62Os0fiQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" @@ -183,81 +183,81 @@ "license": "MIT" }, "node_modules/@algolia/ingestion": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.52.0.tgz", - "integrity": "sha512-U4EeTvgmluRjj39ykZSAd5X+a6LD5m7/mcOWDmB7hqm1R6QY0yT8jLxpNVEjYhzgEN5hcDGW6X67EWQY8KiYGQ==", + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.55.0.tgz", + "integrity": "sha512-80tKsQgxXWo+jK0v4YGCHqyTEXawhAKYyr3kOdN51ElfRqUFjZNPVhZk6vRiqSqXfvrH85ytacT3cbJR6+qolA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.52.0.tgz", - "integrity": "sha512-FCPnDcILfpTE94u7BVlV4DmnSV5wE3+j25EEF+3dYPrVzkVCSoAHs318oWDGxnxsAgiL4HpL12Jc4XHmw9shpA==", + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.55.0.tgz", + "integrity": "sha512-4UjmAL8ywGW4rCfK6Qmgw3wIjbrO2wl2s4Eq56JTiN40L2t0XTv0HZkYAmr6nfeiXO0he/2crvZRX6SATSepag==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.52.0.tgz", - "integrity": "sha512-br3DO7n4N8CXwTRbZS0MnB4WQ9YHfNjCwkCEzVR/wek/qNTDQKDb0nROmkFaNZ8ucUqUVKZi074dbwMwRDlK8Q==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.55.0.tgz", + "integrity": "sha512-LMpJPtIkfDsHIx5Ga+baNr22ntYbY+e2wT7MSIc/FjAnu9wnBFhx1H/GfhmP/c5/IvbThDX+3ilxPRjSfCI8aA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.52.0.tgz", - "integrity": "sha512-b0T/Ca2c9KyEslKsVrGZvbe1UrrKKSdfXhBZ2pbpKahFUzJfziRZ0urbOm7V65O0tO/jwU+Lo/+bIiiyhzGt8w==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.55.0.tgz", + "integrity": "sha512-tDymJ7nFOAoUuecma3usK6o94dp8m4HYFDGh4ByYQXWkv14cpmDn+nWdylmcZO0Qvco107vqDo4+Anksnl8w1Q==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0" + "@algolia/client-common": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.52.0.tgz", - "integrity": "sha512-ozBT8J/mtD4H4IAojw8QPirlcL2gHrI1BGuZ4/ZXXO/rTE1yQ4VIPJj4mTTbwo4FbkS1MoJsD/DsrqLzhnc4/g==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.55.0.tgz", + "integrity": "sha512-6IDSB5o5dkDPQ4LdOW0Yuw/qy5MdWlO2xDHgPVZgW4YDjbxvnX5PAiV7/WWZdWyVObScZZnnHpPbiqfYs/zBLg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0" + "@algolia/client-common": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.52.0.tgz", - "integrity": "sha512-gyyWcLD22tnabmoit4iukCXuoRc5HYJuUjPSEa8a0D/f/NlRafpWi52AlAaa4Uu/rsl7saHsJFTNjTptWbu2+A==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.55.0.tgz", + "integrity": "sha512-Yyyne4l//vDSdg4MhYJkaVne+KEPi833eCj3/T/87ernTwrvP6j9biXXZELsN8sLI/f2ndV/vugDIy2jdJQB6g==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0" + "@algolia/client-common": "5.55.0" }, "engines": { "node": ">= 14.0.0" @@ -277,12 +277,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -291,29 +291,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -339,13 +339,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -355,25 +355,25 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -392,17 +392,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.3.tgz", - "integrity": "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.29.0", + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "engines": { @@ -422,12 +422,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-annotate-as-pure": "^7.29.7", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, @@ -464,49 +464,49 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -516,35 +516,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -554,14 +554,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -571,79 +571,79 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", - "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -653,13 +653,13 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -669,12 +669,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -684,12 +684,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -699,13 +699,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz", - "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -715,14 +715,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -732,13 +732,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", - "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/traverse": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -772,12 +772,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", - "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -787,12 +787,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -802,12 +802,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -817,12 +817,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -848,12 +848,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -863,14 +863,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", - "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.29.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -880,14 +880,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", - "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -897,12 +897,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -912,12 +912,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", - "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -927,13 +927,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", - "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -943,13 +943,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", - "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -959,17 +959,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", - "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -979,13 +979,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", - "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -995,13 +995,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1011,13 +1011,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", - "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1027,12 +1027,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1042,13 +1042,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1058,12 +1058,12 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1073,13 +1073,13 @@ } }, "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", - "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1089,12 +1089,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", - "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1104,12 +1104,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1119,13 +1119,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1135,14 +1135,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1152,12 +1152,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", - "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1167,12 +1167,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1182,12 +1182,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", - "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1197,12 +1197,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1212,13 +1212,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1228,13 +1228,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1244,15 +1244,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", + "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1262,13 +1262,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1278,13 +1278,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1294,12 +1294,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1309,12 +1309,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", - "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1324,12 +1324,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", - "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1339,16 +1339,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", - "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1358,13 +1358,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1374,12 +1374,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", - "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1389,13 +1389,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", - "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1405,12 +1405,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1420,13 +1420,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", - "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1436,14 +1436,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", - "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1453,12 +1453,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1468,12 +1468,12 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", - "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.29.7.tgz", + "integrity": "sha512-J0wGhKan+rIiE2OhfhRptySLrJ6SjQYM6b6N1FMlhyhCcw1Mig8vQjWchyB+bgHGDvaWo6Diu6CLRMra2uMtmg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1483,12 +1483,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", - "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", + "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1498,16 +1498,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", - "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-syntax-jsx": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1517,12 +1517,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", - "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", + "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.27.1" + "@babel/plugin-transform-react-jsx": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1532,13 +1532,13 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", - "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", + "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1548,12 +1548,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", - "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz", + "integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1563,13 +1563,13 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", - "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1579,12 +1579,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1594,13 +1594,13 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", - "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", + "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", @@ -1623,12 +1623,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1638,13 +1638,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", - "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz", + "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1654,12 +1654,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1669,12 +1669,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1684,12 +1684,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1699,16 +1699,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", - "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1718,12 +1718,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1733,13 +1733,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", - "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1749,13 +1749,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1765,13 +1765,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", - "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1781,76 +1781,76 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.3.tgz", - "integrity": "sha512-ySZypNLAIH1ClygLDQzVMoGQRViATnkHkYYV6TcNDz+8+jwZCdsguGvsb3EY5d9wyWyhmF1iSuFM0Yh5XPnqSA==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.28.6", - "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.29.0", - "@babel/plugin-transform-async-to-generator": "^7.28.6", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.6", - "@babel/plugin-transform-class-properties": "^7.28.6", - "@babel/plugin-transform-class-static-block": "^7.28.6", - "@babel/plugin-transform-classes": "^7.28.6", - "@babel/plugin-transform-computed-properties": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.28.6", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.6", - "@babel/plugin-transform-exponentiation-operator": "^7.28.6", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.28.6", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.0", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", - "@babel/plugin-transform-numeric-separator": "^7.28.6", - "@babel/plugin-transform-object-rest-spread": "^7.28.6", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.28.6", - "@babel/plugin-transform-optional-chaining": "^7.28.6", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-private-property-in-object": "^7.28.6", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.29.0", - "@babel/plugin-transform-regexp-modifiers": "^7.28.6", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.28.6", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.28.6", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", @@ -1902,17 +1902,17 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", - "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz", + "integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.28.0", - "@babel/plugin-transform-react-jsx": "^7.27.1", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-react-display-name": "^7.29.7", + "@babel/plugin-transform-react-jsx": "^7.29.7", + "@babel/plugin-transform-react-jsx-development": "^7.29.7", + "@babel/plugin-transform-react-pure-annotations": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1922,16 +1922,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1941,40 +1941,40 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -1982,13 +1982,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2250,9 +2250,9 @@ } }, "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -2671,9 +2671,9 @@ } }, "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -3118,9 +3118,9 @@ } }, "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -4200,9 +4200,9 @@ } }, "node_modules/@easyops-cn/docusaurus-search-local": { - "version": "0.55.1", - "resolved": "https://registry.npmjs.org/@easyops-cn/docusaurus-search-local/-/docusaurus-search-local-0.55.1.tgz", - "integrity": "sha512-jmBKj1J+tajqNrCvECwKCQYTWwHVZDGApy8lLOYEPe+Dm0/f3Ccdw8BP5/OHNpltr7WDNY2roQXn+TWn2f1kig==", + "version": "0.55.2", + "resolved": "https://registry.npmjs.org/@easyops-cn/docusaurus-search-local/-/docusaurus-search-local-0.55.2.tgz", + "integrity": "sha512-dI/riu+MbDxkAjAHAdc0uahjXRaWKvbIPe9IAmA6AGcUfnVb9xd8s2I/6wEPTOXsAd6eFqn4Yis3WBWh3KUd3g==", "license": "MIT", "dependencies": { "@docusaurus/plugin-content-docs": "^2 || ^3", @@ -4309,20 +4309,20 @@ } }, "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.1", + "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "license": "MIT", "optional": true, "dependencies": { @@ -4330,9 +4330,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "license": "MIT", "optional": true, "dependencies": { @@ -4504,13 +4504,13 @@ } }, "node_modules/@jsonjoy.com/fs-core": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.2.tgz", - "integrity": "sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==", + "version": "4.57.7", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.7.tgz", + "integrity": "sha512-GDKuYHjP7vAI1kjBo73V+STKr9XIMZknW/xirpRW/EcShX0IKSev/ALafeRfC8Q331nodrXUFu04PugPB0MAhw==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.7", + "@jsonjoy.com/fs-node-utils": "4.57.7", "thingies": "^2.5.0" }, "engines": { @@ -4525,14 +4525,14 @@ } }, "node_modules/@jsonjoy.com/fs-fsa": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.2.tgz", - "integrity": "sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==", + "version": "4.57.7", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.7.tgz", + "integrity": "sha512-1rWsah2nZtRbNeP+c61QcfGfVrJXBmBD0Hm7Akvv4C9MKEasXnbiOS//iH3T3HwUSSBATGrfSp0Xi8nlNhATeQ==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.57.2", - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-core": "4.57.7", + "@jsonjoy.com/fs-node-builtins": "4.57.7", + "@jsonjoy.com/fs-node-utils": "4.57.7", "thingies": "^2.5.0" }, "engines": { @@ -4547,16 +4547,16 @@ } }, "node_modules/@jsonjoy.com/fs-node": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.2.tgz", - "integrity": "sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==", + "version": "4.57.7", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.7.tgz", + "integrity": "sha512-xhnyeyEVTiIOibFvda/5n89nChMLCPKHHM2WQ+GGDf6+U/IrQBW3Qx6x+Uq1bkDbxBkybLOdIGoBtVBrE8Nngg==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.57.2", - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2", - "@jsonjoy.com/fs-print": "4.57.2", - "@jsonjoy.com/fs-snapshot": "4.57.2", + "@jsonjoy.com/fs-core": "4.57.7", + "@jsonjoy.com/fs-node-builtins": "4.57.7", + "@jsonjoy.com/fs-node-utils": "4.57.7", + "@jsonjoy.com/fs-print": "4.57.7", + "@jsonjoy.com/fs-snapshot": "4.57.7", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, @@ -4572,9 +4572,9 @@ } }, "node_modules/@jsonjoy.com/fs-node-builtins": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.2.tgz", - "integrity": "sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==", + "version": "4.57.7", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.7.tgz", + "integrity": "sha512-LWqfY1m+uAosjwM1RrKhMkUnP9jcq1RUczHsNO779ovm1E9v8I/pmj04eBAcoBjhC7ltcPbNFGyRJ5JqSJ7Jdg==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -4588,14 +4588,14 @@ } }, "node_modules/@jsonjoy.com/fs-node-to-fsa": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.2.tgz", - "integrity": "sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==", + "version": "4.57.7", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.7.tgz", + "integrity": "sha512-9T0zC9LKcAWXDoTLRdLMoJ0seOvJ5bgDKq1tSBoQAFQpPDstQUeV1Oe7PLypdu7F2D3ddRstmwgeNUEN/VaZ4Q==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-fsa": "4.57.2", - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2" + "@jsonjoy.com/fs-fsa": "4.57.7", + "@jsonjoy.com/fs-node-builtins": "4.57.7", + "@jsonjoy.com/fs-node-utils": "4.57.7" }, "engines": { "node": ">=10.0" @@ -4609,12 +4609,12 @@ } }, "node_modules/@jsonjoy.com/fs-node-utils": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.2.tgz", - "integrity": "sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==", + "version": "4.57.7", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.7.tgz", + "integrity": "sha512-jjWSDOsfcog2cZnUCwX5AHmlIq6b6wx5Pz/2LAcNjJ62Rajwg89Fy7ubN+lDHew0/1reLDa9Z5urybYadhh37g==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.57.2" + "@jsonjoy.com/fs-node-builtins": "4.57.7" }, "engines": { "node": ">=10.0" @@ -4628,12 +4628,12 @@ } }, "node_modules/@jsonjoy.com/fs-print": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.2.tgz", - "integrity": "sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==", + "version": "4.57.7", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.7.tgz", + "integrity": "sha512-mFM4P4Gjq0QQHkLnXzPYPEMFrAoe6a5Myedgb6+CmL+nGd3MKvTxYPuD7N1dLIH9RBy1fLdzxd80qvuK8xrx3Q==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.7", "tree-dump": "^1.1.0" }, "engines": { @@ -4648,13 +4648,13 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.2.tgz", - "integrity": "sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==", + "version": "4.57.7", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.7.tgz", + "integrity": "sha512-1GS3+plfm2giB3PqokiqyydyqYTPLcCQIKSkp0TdMNRh3KVk7rqRM6U785FLlVRG7XLmkc0KWr215OY+22K3QA==", "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", - "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.7", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, @@ -4987,15 +4987,15 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", - "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@tybys/wasm-util": "^0.10.1" + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, "node_modules/@noble/hashes": { @@ -5142,6 +5142,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5158,6 +5161,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5174,6 +5180,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5190,6 +5199,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5215,18 +5227,6 @@ "node": ">=14.0.0" } }, - "node_modules/@node-rs/jieba-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, "node_modules/@node-rs/jieba-win32-arm64-msvc": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-arm64-msvc/-/jieba-win32-arm64-msvc-1.10.4.tgz", @@ -5311,128 +5311,137 @@ } }, "node_modules/@peculiar/asn1-cms": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", - "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.8.0.tgz", + "integrity": "sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "@peculiar/asn1-x509-attr": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "@peculiar/asn1-x509-attr": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-csr": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", - "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.8.0.tgz", + "integrity": "sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-ecc": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", - "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.8.0.tgz", + "integrity": "sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pfx": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", - "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.8.0.tgz", + "integrity": "sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg==", "license": "MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.6.1", - "@peculiar/asn1-pkcs8": "^2.6.1", - "@peculiar/asn1-rsa": "^2.6.1", - "@peculiar/asn1-schema": "^2.6.0", - "asn1js": "^3.0.6", + "@peculiar/asn1-cms": "^2.8.0", + "@peculiar/asn1-pkcs8": "^2.8.0", + "@peculiar/asn1-rsa": "^2.8.0", + "@peculiar/asn1-schema": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs8": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", - "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.8.0.tgz", + "integrity": "sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs9": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", - "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.8.0.tgz", + "integrity": "sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ==", "license": "MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.6.1", - "@peculiar/asn1-pfx": "^2.6.1", - "@peculiar/asn1-pkcs8": "^2.6.1", - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "@peculiar/asn1-x509-attr": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-cms": "^2.8.0", + "@peculiar/asn1-pfx": "^2.8.0", + "@peculiar/asn1-pkcs8": "^2.8.0", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "@peculiar/asn1-x509-attr": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-rsa": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", - "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.8.0.tgz", + "integrity": "sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-schema": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", - "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz", + "integrity": "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==", "license": "MIT", "dependencies": { - "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-x509": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", - "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.8.0.tgz", + "integrity": "sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-x509-attr": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", - "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.8.0.tgz", + "integrity": "sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, @@ -5486,9 +5495,9 @@ "license": "ISC" }, "node_modules/@pnpm/npm-conf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", - "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.3.tgz", + "integrity": "sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==", "license": "MIT", "dependencies": { "@pnpm/config.env-replace": "^1.1.0", @@ -5506,27 +5515,27 @@ "license": "MIT" }, "node_modules/@rspack/binding": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.7.11.tgz", - "integrity": "sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.7.12.tgz", + "integrity": "sha512-f4HHuLbvuld8Ba4iB/4ibse5XrKxFrgmM3S4P2AOKnPlekAFlBjmltCuaTL/W2ggYvILaVY+YcFXrEH1rrKeQA==", "license": "MIT", "optionalDependencies": { - "@rspack/binding-darwin-arm64": "1.7.11", - "@rspack/binding-darwin-x64": "1.7.11", - "@rspack/binding-linux-arm64-gnu": "1.7.11", - "@rspack/binding-linux-arm64-musl": "1.7.11", - "@rspack/binding-linux-x64-gnu": "1.7.11", - "@rspack/binding-linux-x64-musl": "1.7.11", - "@rspack/binding-wasm32-wasi": "1.7.11", - "@rspack/binding-win32-arm64-msvc": "1.7.11", - "@rspack/binding-win32-ia32-msvc": "1.7.11", - "@rspack/binding-win32-x64-msvc": "1.7.11" + "@rspack/binding-darwin-arm64": "1.7.12", + "@rspack/binding-darwin-x64": "1.7.12", + "@rspack/binding-linux-arm64-gnu": "1.7.12", + "@rspack/binding-linux-arm64-musl": "1.7.12", + "@rspack/binding-linux-x64-gnu": "1.7.12", + "@rspack/binding-linux-x64-musl": "1.7.12", + "@rspack/binding-wasm32-wasi": "1.7.12", + "@rspack/binding-win32-arm64-msvc": "1.7.12", + "@rspack/binding-win32-ia32-msvc": "1.7.12", + "@rspack/binding-win32-x64-msvc": "1.7.12" } }, "node_modules/@rspack/binding-darwin-arm64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.11.tgz", - "integrity": "sha512-oduECiZVqbO5zlVw+q7Vy65sJFth99fWPTyucwvLJJtJkPL5n17Uiql2cYP6Ijn0pkqtf1SXgK8WjiKLG5bIig==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.12.tgz", + "integrity": "sha512-rbFprJaJiqrmfy8SHth8EsoRS0wg4bXcucwj9NiMzpGFq14Opw8c04iQ6H9BECYzgmN0PKZ9rh41LdVvhdZe4A==", "cpu": [ "arm64" ], @@ -5537,9 +5546,9 @@ ] }, "node_modules/@rspack/binding-darwin-x64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.11.tgz", - "integrity": "sha512-a1+TtTE9ap6RalgFi7FGIgkJP6O4Vy6ctv+9WGJy53E4kuqHR0RygzaiVxCI/GMc/vBT9vY23hyrpWb3d1vtXA==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.12.tgz", + "integrity": "sha512-jnOp+/UXOJa9xqUb8KXH03sysoO2e4Ij6tw6MqDdmdj8n/A8PQENRPUbW9AwXpPtVDJPus9r4fi7b3+6e4B8Hg==", "cpu": [ "x64" ], @@ -5550,12 +5559,15 @@ ] }, "node_modules/@rspack/binding-linux-arm64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.11.tgz", - "integrity": "sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.12.tgz", + "integrity": "sha512-C8owWG+yvo7X0oVLIXetkoJhIFBP1LYNcAQqtgLmJnQLQDklGuP83dKC+zISGQWpjawHfZ1ER96vLgoTrxKZdw==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5563,12 +5575,15 @@ ] }, "node_modules/@rspack/binding-linux-arm64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.11.tgz", - "integrity": "sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.12.tgz", + "integrity": "sha512-i51WWI64aRpsfSki6rN0aepPqXkVfS+vZM7+4bWDcmnhUmdMvhIPcYg0QRk3DtyJnu33jqNLM0WHY78k00NyfA==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5576,12 +5591,15 @@ ] }, "node_modules/@rspack/binding-linux-x64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.11.tgz", - "integrity": "sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.12.tgz", + "integrity": "sha512-MSos0FuPEefqo9V92ULd5hggKG29EkSNg1zDcypy0OkpsKh5pfjVxTLYFXgTcVyFoUQQbdG8zFBzYbwmJ8V4ew==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5589,12 +5607,15 @@ ] }, "node_modules/@rspack/binding-linux-x64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.11.tgz", - "integrity": "sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.12.tgz", + "integrity": "sha512-JcAMVKXOnjfpC3coWjCFPWD3Yl8RBw6a+IXQQ8mfRlHaHMIiOv8IfZqx15XRxMUn49CtP7Z0Na8iiAg2aKrcfw==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5602,9 +5623,9 @@ ] }, "node_modules/@rspack/binding-wasm32-wasi": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.11.tgz", - "integrity": "sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.12.tgz", + "integrity": "sha512-n+ZqP6ZMc0nhOgvadg5VhEs9ojtbES80AcWeFnmGkbzIszvGSO63GKNiRkXtjJ9KFuRzytbbmsCqkUVH+Tywxg==", "cpu": [ "wasm32" ], @@ -5614,10 +5635,22 @@ "@napi-rs/wasm-runtime": "1.0.7" } }, + "node_modules/@rspack/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, "node_modules/@rspack/binding-win32-arm64-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.11.tgz", - "integrity": "sha512-lObFW6e5lCWNgTBNwT//yiEDbsxm9QG4BYUojqeXxothuzJ/L6ibXz6+gLMvbOvLGV3nKgkXmx8GvT9WDKR0mA==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.12.tgz", + "integrity": "sha512-8+h5fYDXYdmugbdfZ+D1y8IQ3rv2EhSfyGP7vBe+bjNyaMa4jWrpucmZbtxojUL1AzaeuHbvMdj9UO/gelk/+g==", "cpu": [ "arm64" ], @@ -5628,9 +5661,9 @@ ] }, "node_modules/@rspack/binding-win32-ia32-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.11.tgz", - "integrity": "sha512-0pYGnZd8PPqNR68zQ8skamqNAXEA1sUfXuAdYcknIIRq2wsbiwFzIc0Pov1cIfHYab37G7sSIPBiOUdOWF5Ivw==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.12.tgz", + "integrity": "sha512-cDMGwTRSa2p9fNBVe1wTRkF2AEXZ9ARWW36QeC5CkLaI0Ezz8lvhF2+CSOPnhaQ1O1qtn0L0SF+lFnrY+I7xGQ==", "cpu": [ "ia32" ], @@ -5641,9 +5674,9 @@ ] }, "node_modules/@rspack/binding-win32-x64-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.11.tgz", - "integrity": "sha512-EeQXayoQk/uBkI3pdoXfQBXNIUrADq56L3s/DFyM2pJeUDrWmhfIw2UFIGkYPTMSCo8F2JcdcGM32FGJrSnU0Q==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.12.tgz", + "integrity": "sha512-wIqFvlgFqrgUyj/6S/FJcvShnkZOmIeXTfqvheLY67MGq8qd8jb1YimQVKAIrmWB3yuJKUFACI3Ag1UBtEedEA==", "cpu": [ "x64" ], @@ -5654,13 +5687,13 @@ ] }, "node_modules/@rspack/core": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.7.11.tgz", - "integrity": "sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.7.12.tgz", + "integrity": "sha512-6CwFIHlhRmXfZoMj3v9MZ1SMTPBn+cHVXeMIeaGp5sufqinKsISbsqHu6ZMJu2wDSmZLdmQJX6zLxkhcAUlhkQ==", "license": "MIT", "dependencies": { "@module-federation/runtime-tools": "0.22.0", - "@rspack/binding": "1.7.11", + "@rspack/binding": "1.7.12", "@rspack/lite-tapable": "1.1.0" }, "engines": { @@ -5989,9 +6022,9 @@ } }, "node_modules/@swc/core": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.32.tgz", - "integrity": "sha512-/eWL0n43D64QWEUHLtTE+jDqjkJhyidjkDhv6f0uJohOUAhywxQ9wXYp845DNNds0JpCdI4Uo0a9bl+vbXf+ew==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.41.tgz", + "integrity": "sha512-03nQq/082QRJJiOvp3FGbgxTGyyxMxohPTjhk/W9bD2J0tk4ukITI7goOhOO2WbaHn/lsPmo/zf8+DIXhwpgYQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -6006,18 +6039,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.32", - "@swc/core-darwin-x64": "1.15.32", - "@swc/core-linux-arm-gnueabihf": "1.15.32", - "@swc/core-linux-arm64-gnu": "1.15.32", - "@swc/core-linux-arm64-musl": "1.15.32", - "@swc/core-linux-ppc64-gnu": "1.15.32", - "@swc/core-linux-s390x-gnu": "1.15.32", - "@swc/core-linux-x64-gnu": "1.15.32", - "@swc/core-linux-x64-musl": "1.15.32", - "@swc/core-win32-arm64-msvc": "1.15.32", - "@swc/core-win32-ia32-msvc": "1.15.32", - "@swc/core-win32-x64-msvc": "1.15.32" + "@swc/core-darwin-arm64": "1.15.41", + "@swc/core-darwin-x64": "1.15.41", + "@swc/core-linux-arm-gnueabihf": "1.15.41", + "@swc/core-linux-arm64-gnu": "1.15.41", + "@swc/core-linux-arm64-musl": "1.15.41", + "@swc/core-linux-ppc64-gnu": "1.15.41", + "@swc/core-linux-s390x-gnu": "1.15.41", + "@swc/core-linux-x64-gnu": "1.15.41", + "@swc/core-linux-x64-musl": "1.15.41", + "@swc/core-win32-arm64-msvc": "1.15.41", + "@swc/core-win32-ia32-msvc": "1.15.41", + "@swc/core-win32-x64-msvc": "1.15.41" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -6029,9 +6062,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.32.tgz", - "integrity": "sha512-/YWMvJDPu+AAwuUsM2G+DNQ/7zhodURGzdQyewEqcvgklAdDHs3LwQmLLnyn6SJl8DT8UOxkbzK+D1PmPeelRg==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.41.tgz", + "integrity": "sha512-kREh6J5paQFvP3i7f/4FbqRNOJREutVFVOkder4GVyCBQ39YmER55cW/y1NNjwrchzFqgYswFn0mMDCqbqKzrw==", "cpu": [ "arm64" ], @@ -6045,9 +6078,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.32.tgz", - "integrity": "sha512-KOTXJXdAhWL+hZ77MYP3z+4pcMFaQhQ74yqyN1uz093q0YnbxpqMtYpPISbYvMHzVRNNx5kN+9RZAXEaadhWVA==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.41.tgz", + "integrity": "sha512-N8B56ESFazZAWZyIkecADSPCwlLEinW7QLMEeotCpv4J7VXwfH+OLkmRL8o96UZ+1355fwHxDTS6/wK7yucvkA==", "cpu": [ "x64" ], @@ -6061,9 +6094,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.32.tgz", - "integrity": "sha512-oOoxLweljlc0A4X8ybsgxV7cVaYTwBOg2iMDJcFR3Sr48C+lsv9VzSmqdK/IVIXF4W4GjLc3VqTAdSMXlfVLuQ==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.41.tgz", + "integrity": "sha512-6XrId2fyle0mS5xxON8rU84mPd2Cq1kDJRj+4BnQKTd7u+2kSA6Ww+JkOP0iTNqOqt9OXhPOEAjBHAuonWcdCg==", "cpu": [ "arm" ], @@ -6077,12 +6110,15 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.32.tgz", - "integrity": "sha512-oDzEkdl6D6BAWdMtU5KGO7y3HR5fJcvByNLyEk9+ugj8nP5Ovb7P4kBcStBXc4MPExFGQryehiINMlmY8HlclA==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.41.tgz", + "integrity": "sha512-ynLIarxlkVnqHn1D0fKOVht6mNU5ks6lrH+MY3kkS+XFaGGgDxFZVjWKJlkYTKm3RCvBTfA8Ng5fLufXheMRKQ==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6093,12 +6129,15 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.32.tgz", - "integrity": "sha512-omcqjoZP/b8D8PuczVoRwJieC6ibj7qIxTftNYokz4/aSmKFHvsd7nIFfPk5ZvtzncbH4AY7+Dkr/Lp2gWxYeA==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.41.tgz", + "integrity": "sha512-dXu/5vd4gh8symyhRF+4G7gOPkjmb4pONhh7sl+6GSiW0LOKZlfu5kXmyFbTz9smOT7jgr002qY9b1nujjXt2A==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6109,12 +6148,15 @@ } }, "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.32.tgz", - "integrity": "sha512-KGkTMyz/Tbn3PBNu0AVZ4GTDFKnICrYcTiNPZq8DrvK42pnFsf3GNDrIG9E5AtQlTmC0YigkWKmu0eMcfTrmgA==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.41.tgz", + "integrity": "sha512-XGO6zVPXoPE0gf/XnI4jBbafNT13AYgoh6ns0JCSdOetI/kqVf0vhpz7NuNgAzZrMVCsmieqjPoTwViDgh4mOQ==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6125,12 +6167,15 @@ } }, "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.32.tgz", - "integrity": "sha512-G3Aa4tVS/3OGZBkoNIwUF9F6RAy+Osb4GOlo62SinLmDiErz/ykmM7KH0wkz6l9kM8jJq1HyAM6atJTUEbBk7g==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.41.tgz", + "integrity": "sha512-0WUglRwyZtW+iMi7J3iFdrCxreZZIKf4egTwEQfIYRsqFax69A0OrFj+NIoFSE03xBT/IFRrg+S8K6f9Ky+4hA==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6141,12 +6186,15 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.32.tgz", - "integrity": "sha512-ERsjfGcj6CBmj3vJnGDO8m8rTvw6RqMcWo1dogOtNx3/+/0+NNpJiXDobJrr1GwInI/BHAEkvSFIH6d2LqPcUQ==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.41.tgz", + "integrity": "sha512-VxkuQK59c0tHm6uJZCUrS3cyA2JhGGfdU6e41SZz0x/JS+4Sm7C1mIc97In14vkZJopEt7yXA2TouCqZDSygEA==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6157,12 +6205,15 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.32.tgz", - "integrity": "sha512-N4Ggahe/8SUbTX50P6EdhbW9YWcgbZVb52R4cq6MK+zsoMjRq7rGvV5ztA05QnbaCYqMYx8rTY7KAIA3Crdo4Q==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.41.tgz", + "integrity": "sha512-/0qXIu1ZxggLuovLb22vFfKHq2AA4n6Whw5UwmVCHk4pkw7KWnPIQpMCEqUMPsNkFJig7PPp/TSYFu8ZEb2rtQ==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6173,9 +6224,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.32.tgz", - "integrity": "sha512-01yN0o9jvo8xBTP12aPK2wW8b41jmOlGbDDlAnoynotc4pO6xA0zby9f1z6j++qXDpGBttLySq1omgVrlQKYcw==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.41.tgz", + "integrity": "sha512-Y481sMNZM6rECh9VO4+y26N1lWEDAyxnBZskUf37fl90uHE946VHfmiVQWT0uMFOhyJJFovGTRuF4W82dwewUg==", "cpu": [ "arm64" ], @@ -6189,9 +6240,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.32.tgz", - "integrity": "sha512-fLagI9XZYNpTcmlqAcp3KBtmj7E19WCmYD80Jxj1Kn5tGNa7yxNLd3NNdWxuZGUPl5iC0/KqZru7g08gF6Fsrw==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.41.tgz", + "integrity": "sha512-BAchBD5qeUzy3hiPSLJtaaoSm4blCLyYffOF1bGE4ETcV+OisqjUAwDQMJj++4bTpvMCDzwC+Bj3PmQyBCtscw==", "cpu": [ "ia32" ], @@ -6205,9 +6256,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.32.tgz", - "integrity": "sha512-gbc2bQ/T2CiR+w0OvcVKwLOFAcPZBvmWmolbwpg1E8UrpeC03DGtyMUApOHNXNYWA3SHFrYXCQtosrcMza1YFg==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.41.tgz", + "integrity": "sha512-WOkA+fJ/ViVBQDsSV9JC52NACTe5PhlurA6viASDZGb7HR3KS01ZG7RZ+Bg6SVQFIoq3gSbTsskQVe6EbHFAYw==", "cpu": [ "x64" ], @@ -6227,9 +6278,9 @@ "license": "Apache-2.0" }, "node_modules/@swc/html": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html/-/html-1.15.32.tgz", - "integrity": "sha512-Mv37uFfZQt7j89U3KJPqeQt6vl5Bxk7aqOrNDKWRAmrQOJ+lYJKq4hmYWW6Rk3wdGw03SlEfK3RmnzCN9gsqAA==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html/-/html-1.15.41.tgz", + "integrity": "sha512-FHoXxI56hDKagLxpv5t8ovSPRecBufmHCAhGUuhFQqarfmxGX4g1bTfjQzwuRGRqtWm3IK14oX+TMOM10g51wQ==", "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -6238,24 +6289,24 @@ "node": ">=14" }, "optionalDependencies": { - "@swc/html-darwin-arm64": "1.15.32", - "@swc/html-darwin-x64": "1.15.32", - "@swc/html-linux-arm-gnueabihf": "1.15.32", - "@swc/html-linux-arm64-gnu": "1.15.32", - "@swc/html-linux-arm64-musl": "1.15.32", - "@swc/html-linux-ppc64-gnu": "1.15.32", - "@swc/html-linux-s390x-gnu": "1.15.32", - "@swc/html-linux-x64-gnu": "1.15.32", - "@swc/html-linux-x64-musl": "1.15.32", - "@swc/html-win32-arm64-msvc": "1.15.32", - "@swc/html-win32-ia32-msvc": "1.15.32", - "@swc/html-win32-x64-msvc": "1.15.32" + "@swc/html-darwin-arm64": "1.15.41", + "@swc/html-darwin-x64": "1.15.41", + "@swc/html-linux-arm-gnueabihf": "1.15.41", + "@swc/html-linux-arm64-gnu": "1.15.41", + "@swc/html-linux-arm64-musl": "1.15.41", + "@swc/html-linux-ppc64-gnu": "1.15.41", + "@swc/html-linux-s390x-gnu": "1.15.41", + "@swc/html-linux-x64-gnu": "1.15.41", + "@swc/html-linux-x64-musl": "1.15.41", + "@swc/html-win32-arm64-msvc": "1.15.41", + "@swc/html-win32-ia32-msvc": "1.15.41", + "@swc/html-win32-x64-msvc": "1.15.41" } }, "node_modules/@swc/html-darwin-arm64": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-darwin-arm64/-/html-darwin-arm64-1.15.32.tgz", - "integrity": "sha512-WgY386nwyz24cTJ+Nztd4cKvfPJexLYAzurSYDmuYxS3HihWoTFZWMDomTfM8yr2UCi8SwW+zTNAWxJxUaKESg==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-darwin-arm64/-/html-darwin-arm64-1.15.41.tgz", + "integrity": "sha512-twmx/p6DjOwvuVEl7R449fTDsDy6sBGaxXBqp/+J8LWlJeonsqdy3IXNLcSPDDU90EYl4KyhVqH/bhjt6COGrA==", "cpu": [ "arm64" ], @@ -6269,9 +6320,9 @@ } }, "node_modules/@swc/html-darwin-x64": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-darwin-x64/-/html-darwin-x64-1.15.32.tgz", - "integrity": "sha512-uge7XExmbPREWO+2dZQvAbeiAvUlR+3TxxgYETJw39f8Nlclc3rWXaievpO3iRPbg1s8BsZ9fGGhoN7yYrwUwg==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-darwin-x64/-/html-darwin-x64-1.15.41.tgz", + "integrity": "sha512-VUoct8+4lz7owLlW0KSe5ljjSONZMWGpQbTAhSv++HuNPA/hZCuVoGY16PjRLF9u03zrWLDRqn2CCEdRTrUrSw==", "cpu": [ "x64" ], @@ -6285,9 +6336,9 @@ } }, "node_modules/@swc/html-linux-arm-gnueabihf": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-linux-arm-gnueabihf/-/html-linux-arm-gnueabihf-1.15.32.tgz", - "integrity": "sha512-EuserzRHqXX6R6KScuBn+U3IX9ll3j4+sHM2Y3J/vIH7TbQ5IrvCFuu8w7El5R9j0ByCWvsDa2QdiLCQtsmFlw==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-linux-arm-gnueabihf/-/html-linux-arm-gnueabihf-1.15.41.tgz", + "integrity": "sha512-vDlyud/W/KhnsHD87tvHrF+WXzf4iaJBWM8XdUQ2DKpS7nlK+2qHNAphcVZqu92qZavkvWgWBBgzSNK2And+Ag==", "cpu": [ "arm" ], @@ -6301,12 +6352,15 @@ } }, "node_modules/@swc/html-linux-arm64-gnu": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-gnu/-/html-linux-arm64-gnu-1.15.32.tgz", - "integrity": "sha512-gvlByySjNDWX2FUIGVBWOhd00rySz0AOydQpuXCK0ldYbFVMby9oXbp2JVmE5UsB6J4YZqZh4ajmmqCGvFHi4Q==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-gnu/-/html-linux-arm64-gnu-1.15.41.tgz", + "integrity": "sha512-dEMS/oCMk2KYrQZzRSKDj7hGSMA/UNQ28lVdI0SgWmkUXikFvBCyLBWS50FxnGvquotI3FaoIkGmm+pKJEzMeQ==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6317,12 +6371,15 @@ } }, "node_modules/@swc/html-linux-arm64-musl": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-musl/-/html-linux-arm64-musl-1.15.32.tgz", - "integrity": "sha512-iTrXjSeVwhHp+w7I5srCSpG9prebj+j/lmWalljNXGagTuVmtEWdH/EDvtSq1JfJyKQ6KRKyeB3Qg6yGHhjr+Q==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-musl/-/html-linux-arm64-musl-1.15.41.tgz", + "integrity": "sha512-1yFYeHlyP73BGHTtzaiZoiWTr/NfWkhMKXK+Fm8AH5NNQ99XfP48nDEwWAN7ubwNanDsGw1EELriRucEnxvCQg==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6333,12 +6390,15 @@ } }, "node_modules/@swc/html-linux-ppc64-gnu": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-linux-ppc64-gnu/-/html-linux-ppc64-gnu-1.15.32.tgz", - "integrity": "sha512-sh6yGlZk7YqaQ4XisqEe0tBSTy0WcwmEnMEq9EG6mIU16PCGTbROHMfTw0W81jtvUyjqtCQC9axbSJLAW3zIeA==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-linux-ppc64-gnu/-/html-linux-ppc64-gnu-1.15.41.tgz", + "integrity": "sha512-XIf8fgQ5rEj1y7xjCJZHEvKDMYtAcGfsk8wWGLeUAkYvZhNGEnH2OS2BAZmgXNaaWATmaG8KDM1G2HoluhJpBA==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6349,12 +6409,15 @@ } }, "node_modules/@swc/html-linux-s390x-gnu": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-linux-s390x-gnu/-/html-linux-s390x-gnu-1.15.32.tgz", - "integrity": "sha512-zBavh0QnsvjM9QMPmtOqMaUGSLr5tdj2gxEx4xXzOXBFkUKKRA+qEfx6LdTzIbrqqtK5Xf6CPBPT9kzhDLuaCA==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-linux-s390x-gnu/-/html-linux-s390x-gnu-1.15.41.tgz", + "integrity": "sha512-mE39odiWiZcBBxMfUYgzsZ4+LpOg/eQj8aQyMkTciiKpcurokcRzcUiilMSXtUleVDcLnL1BG1YkHFyyy9rE5Q==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6365,12 +6428,15 @@ } }, "node_modules/@swc/html-linux-x64-gnu": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-linux-x64-gnu/-/html-linux-x64-gnu-1.15.32.tgz", - "integrity": "sha512-IveuScZfAwDZEBs6pTvdG/MwGyMPuxp74l9ngp2PbUboVBIfUS894kATBaBuSBYXajZ4v4wqv01PGM81rUhGQg==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-linux-x64-gnu/-/html-linux-x64-gnu-1.15.41.tgz", + "integrity": "sha512-/etNlaoe+6KVYZ0/BVLvo5/Zcf/f69Cuw0lBSGrCQUr/GP1pKERppmxQqy+onVVT1ckMaur0KR8p9MMI2us7gA==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6381,12 +6447,15 @@ } }, "node_modules/@swc/html-linux-x64-musl": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-linux-x64-musl/-/html-linux-x64-musl-1.15.32.tgz", - "integrity": "sha512-wRXdcS0eaYU1Pm15gNGmVM7fsJ/xCxy3BnfNH52MC/ObgCIzBcFl3Yjn6yr6nkqNtDZyEt8IlQFQno5zEEvIpw==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-linux-x64-musl/-/html-linux-x64-musl-1.15.41.tgz", + "integrity": "sha512-Pzb5QRI0YcInNHShkGW36zypKLCNV/iC60S867PQNRiHnq7igY0z8r6UuLJUhL4EeNA0OVwF+/V5Eqtav38+4w==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -6397,9 +6466,9 @@ } }, "node_modules/@swc/html-win32-arm64-msvc": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-win32-arm64-msvc/-/html-win32-arm64-msvc-1.15.32.tgz", - "integrity": "sha512-GzQQkdi4kC5ZjKloTQXgsI9FNQYb3z1mmF9ZbVDykdRgzKnqWGbx/gwTcWUhiurI9KsyL1t95PmOnU11Jw/mqg==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-win32-arm64-msvc/-/html-win32-arm64-msvc-1.15.41.tgz", + "integrity": "sha512-hT0dZeNThmaU6JQ3R8LKetCyhYDjah8nDjYGNg++RYrJX9Q/iFu5GhL2GGwcTHdwR+XqpNlmpfGBA4djw1I71w==", "cpu": [ "arm64" ], @@ -6413,9 +6482,9 @@ } }, "node_modules/@swc/html-win32-ia32-msvc": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-win32-ia32-msvc/-/html-win32-ia32-msvc-1.15.32.tgz", - "integrity": "sha512-BJqmiTbCWcd1hG9nLEktIASTv0SD91cIKHdt8Zza7AvSjH+BmDX0AW8krVDsJpXWQEtWEYzroe9rweNOMSVfjQ==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-win32-ia32-msvc/-/html-win32-ia32-msvc-1.15.41.tgz", + "integrity": "sha512-PBihRAO6II8L34AafDxQGea9f/Sw+l3btlrODthJF/VasxQPDIitbGKHZdWMry0qLBIzpOdX30h3p3ZD3o3/jQ==", "cpu": [ "ia32" ], @@ -6429,9 +6498,9 @@ } }, "node_modules/@swc/html-win32-x64-msvc": { - "version": "1.15.32", - "resolved": "https://registry.npmjs.org/@swc/html-win32-x64-msvc/-/html-win32-x64-msvc-1.15.32.tgz", - "integrity": "sha512-8uOl327V1nCISIILtpQWrY8dl4JFo8UK5TCFcpMJ8ldt4wggr7cPnvkp2bJkT/pL7djjrDJQZ2BpNfu62M2zWA==", + "version": "1.15.41", + "resolved": "https://registry.npmjs.org/@swc/html-win32-x64-msvc/-/html-win32-x64-msvc-1.15.41.tgz", + "integrity": "sha512-o+9kX1Q6EwpVrrn7uEn/CxXEJDt3n6oLChIwkJFFK2Fh0XjPfOKzlNLOUdwjx6EihRXw1lqQRu/+mE+eS5bIUw==", "cpu": [ "x64" ], @@ -6445,9 +6514,9 @@ } }, "node_modules/@swc/types": { - "version": "0.1.26", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", - "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz", + "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==", "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -6466,9 +6535,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "license": "MIT", "optional": true, "dependencies": { @@ -6775,30 +6844,10 @@ "@types/ms": "*" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -6928,9 +6977,9 @@ } }, "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", "license": "MIT" }, "node_modules/@types/mime": { @@ -6946,12 +6995,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": "~8.3.0" } }, "node_modules/@types/prismjs": { @@ -6961,9 +7010,9 @@ "license": "MIT" }, "node_modules/@types/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", "license": "MIT" }, "node_modules/@types/range-parser": { @@ -6973,9 +7022,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -7114,9 +7163,9 @@ "license": "MIT" }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", "license": "ISC" }, "node_modules/@upsetjs/venn.js": { @@ -7331,9 +7380,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -7443,34 +7492,34 @@ } }, "node_modules/algoliasearch": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.52.0.tgz", - "integrity": "sha512-0ZzY9mjqV7gop/AH8pIBiAS8giXP7WcSiUfoFYIzYAK9QC5c37E4SIVtJVBMwlURc0/uNt2o4RcNRvdHa4CJ5w==", - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.18.0", - "@algolia/client-abtesting": "5.52.0", - "@algolia/client-analytics": "5.52.0", - "@algolia/client-common": "5.52.0", - "@algolia/client-insights": "5.52.0", - "@algolia/client-personalization": "5.52.0", - "@algolia/client-query-suggestions": "5.52.0", - "@algolia/client-search": "5.52.0", - "@algolia/ingestion": "1.52.0", - "@algolia/monitoring": "1.52.0", - "@algolia/recommend": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.55.0.tgz", + "integrity": "sha512-af+rI+tUVeS9KWHPAZQHIHPOIC3StPRR6IwQu2nz1aQoTL6Gs5Ty3KsHCgbXMHOpoh9QqSjq8F3KJ8xmaCZSBA==", + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.21.0", + "@algolia/client-abtesting": "5.55.0", + "@algolia/client-analytics": "5.55.0", + "@algolia/client-common": "5.55.0", + "@algolia/client-insights": "5.55.0", + "@algolia/client-personalization": "5.55.0", + "@algolia/client-query-suggestions": "5.55.0", + "@algolia/client-search": "5.55.0", + "@algolia/ingestion": "1.55.0", + "@algolia/monitoring": "1.55.0", + "@algolia/recommend": "5.55.0", + "@algolia/requester-browser-xhr": "5.55.0", + "@algolia/requester-fetch": "5.55.0", + "@algolia/requester-node-http": "5.55.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.28.2", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.28.2.tgz", - "integrity": "sha512-sexVcXLHrJN54+S0wXD52xV3ySeGZA5T6HMDkb84wT+3UcXCd8af/k2vU5qJTbHv7DoBb4mISJHdyQ2JOo3Aig==", + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.29.1.tgz", + "integrity": "sha512-6ck2YFudF2Pje7szQoPBiRFTGfd+1I+0I/WfLPGn0bj1kvrFoOQmNyedNiDxTk3/r4IfSLDYk+RA4G7u8H6+yA==", "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" @@ -7743,9 +7792,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.24", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", - "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -7823,31 +7872,28 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.1.tgz", + "integrity": "sha512-9KM4QMPKnaJqaja1v7gYO/+TXZGLtzPA05NmUTqDAJjcsWeVoOXKMvU9g0gfuuoYTQqJZ924hivICd5R/bCJbA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -7883,9 +7929,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -8094,9 +8140,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001791", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", "funding": [ { "type": "opencollective", @@ -8826,9 +8872,9 @@ } }, "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -8900,9 +8946,9 @@ } }, "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -9055,9 +9101,9 @@ } }, "node_modules/cssdb": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.8.0.tgz", - "integrity": "sha512-QbLeyz2Bgso1iRlh7IpWk6OKa3lLNGXsujVjDMPl9rOZpxKeiG69icLpbLCFxeURwmcdIfZqQyhlooKJYM4f8Q==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.9.0.tgz", + "integrity": "sha512-J8jOU/hLjaXcO1LldOLraJSQpfLXRKof0I7mtbRyOy2AAXgqst0x9rlgi2qXeD6d0ou3ZLqcPAMqYVbpCbrxEw==", "funding": [ { "type": "opencollective", @@ -9445,18 +9491,6 @@ "node": ">= 10" } }, - "node_modules/d3-dsv/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -10062,9 +10096,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.8.tgz", - "integrity": "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==", + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", + "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -10151,9 +10185,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.346", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.346.tgz", - "integrity": "sha512-3PGbvVwt9AppQsta0Kuq5DIcSj7aQfDfCVS7KnV3nhXEDtuJVRS7kK28Q+qy5KRkQ4bICV4xOaXNeUaXe78dDg==", + "version": "1.5.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", + "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -10209,22 +10243,10 @@ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" } }, - "node_modules/encoding-sniffer/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/enhanced-resolve": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", - "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz", + "integrity": "sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -10280,9 +10302,9 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -10292,9 +10314,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz", - "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==", + "version": "1.47.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.1.tgz", + "integrity": "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==", "license": "MIT", "workspaces": [ "docs", @@ -10612,14 +10634,14 @@ } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "~1.20.3", + "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", @@ -10638,7 +10660,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "~6.14.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -10746,9 +10768,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -11031,9 +11053,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -11371,9 +11393,9 @@ } }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -11883,12 +11905,12 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -12035,9 +12057,9 @@ } }, "node_modules/ipaddr.js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", - "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", "license": "MIT", "engines": { "node": ">= 10" @@ -12098,12 +12120,12 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -12236,9 +12258,9 @@ } }, "node_modules/is-network-error": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", - "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", "license": "MIT", "engines": { "node": ">=16" @@ -12436,9 +12458,9 @@ } }, "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "version": "17.13.4", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz", + "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.3.0", @@ -12455,9 +12477,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -12602,13 +12634,13 @@ } }, "node_modules/launch-editor": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", - "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", "license": "MIT", "dependencies": { "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" + "shell-quote": "^1.8.4" } }, "node_modules/layout-base": { @@ -12762,6 +12794,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -12782,6 +12817,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -12802,6 +12840,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -12822,6 +12863,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -13024,9 +13068,9 @@ "license": "MIT" }, "node_modules/lunr-languages": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.14.0.tgz", - "integrity": "sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.20.0.tgz", + "integrity": "sha512-3LVgE7ekWXt04NBci/hjm+NXJxXZeRXuyClL0kA0HONyBOjxhP3ZQkuWIM4Ok3pbeptUW/rj3XcJcJuJVPwPYA==", "license": "MPL-1.1" }, "node_modules/mark.js": { @@ -13496,19 +13540,19 @@ } }, "node_modules/memfs": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.2.tgz", - "integrity": "sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==", + "version": "4.57.7", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.7.tgz", + "integrity": "sha512-YZPphUQZSRGk6ddPlsNuMbztrLwsbUATFNZcqKscSbSJZ4g0+Y3vSZLJ/rfnGZaB1FFhC7SrywZXev6i8lnHgg==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.57.2", - "@jsonjoy.com/fs-fsa": "4.57.2", - "@jsonjoy.com/fs-node": "4.57.2", - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-to-fsa": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2", - "@jsonjoy.com/fs-print": "4.57.2", - "@jsonjoy.com/fs-snapshot": "4.57.2", + "@jsonjoy.com/fs-core": "4.57.7", + "@jsonjoy.com/fs-fsa": "4.57.7", + "@jsonjoy.com/fs-node": "4.57.7", + "@jsonjoy.com/fs-node-builtins": "4.57.7", + "@jsonjoy.com/fs-node-to-fsa": "4.57.7", + "@jsonjoy.com/fs-node-utils": "4.57.7", + "@jsonjoy.com/fs-print": "4.57.7", + "@jsonjoy.com/fs-snapshot": "4.57.7", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", @@ -15513,9 +15557,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.13.tgz", + "integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==", "funding": [ { "type": "github", @@ -15571,10 +15615,13 @@ } }, "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", - "license": "MIT" + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -16206,9 +16253,9 @@ } }, "node_modules/postcss": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", - "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "funding": [ { "type": "opencollective", @@ -16225,7 +16272,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -16259,9 +16306,9 @@ } }, "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -16503,9 +16550,9 @@ } }, "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -16541,9 +16588,9 @@ } }, "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -16669,9 +16716,9 @@ } }, "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -16707,9 +16754,9 @@ } }, "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -16996,9 +17043,9 @@ } }, "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -17024,9 +17071,9 @@ } }, "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -17123,9 +17170,9 @@ } }, "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -17486,9 +17533,9 @@ } }, "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -17579,9 +17626,9 @@ } }, "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -17592,9 +17639,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -17740,9 +17787,9 @@ } }, "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "license": "MIT", "funding": { "type": "github", @@ -17820,9 +17867,9 @@ } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -17899,6 +17946,18 @@ "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -17930,24 +17989,24 @@ } }, "node_modules/react": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.5" + "react": "^19.2.7" } }, "node_modules/react-fast-compare": { @@ -18240,9 +18299,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", - "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -18800,9 +18859,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -19074,9 +19133,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -19086,14 +19145,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -19623,9 +19682,9 @@ } }, "node_modules/terser": { - "version": "5.46.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz", - "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", + "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -19641,9 +19700,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz", - "integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -19662,12 +19721,39 @@ "webpack": "^5.1.0" }, "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, "@swc/core": { "optional": true }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, "esbuild": { "optional": true }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, "uglify-js": { "optional": true } @@ -19827,9 +19913,9 @@ } }, "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", "license": "MIT", "engines": { "node": ">=6.10" @@ -19929,18 +20015,18 @@ } }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "license": "MIT", "engines": { "node": ">=20.18.1" } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -20436,12 +20522,11 @@ } }, "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", "license": "MIT", "dependencies": { - "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" }, "engines": { @@ -20468,12 +20553,11 @@ } }, "node_modules/webpack": { - "version": "5.106.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", - "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", + "version": "5.107.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.107.2.tgz", + "integrity": "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==", "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", @@ -20483,20 +20567,20 @@ "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.20.0", - "es-module-lexer": "^2.0.0", + "enhanced-resolve": "^5.22.0", + "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", - "loader-runner": "^4.3.1", + "loader-runner": "^4.3.2", "mime-db": "^1.54.0", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.17", + "terser-webpack-plugin": "^5.5.0", "watchpack": "^2.5.1", - "webpack-sources": "^3.3.4" + "webpack-sources": "^3.5.0" }, "bin": { "webpack": "bin/webpack.js" @@ -20613,9 +20697,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", - "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.5.tgz", + "integrity": "sha512-4wZtCquSuv9CKX8oybo+mqxtxZqWz47uM1Ch94lxowBztOhWCbhqvRbfC/mODOwxgV2brY+JGZpHq58/SuVFYg==", "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", @@ -20700,9 +20784,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -20735,9 +20819,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.1.tgz", - "integrity": "sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", + "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", "license": "MIT", "engines": { "node": ">=10.13.0" @@ -20780,9 +20864,9 @@ } }, "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", @@ -20815,18 +20899,6 @@ "node": ">=18" } }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -20941,9 +21013,9 @@ } }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", "engines": { "node": ">=8.3.0" diff --git a/website/sidebars.ts b/website/sidebars.ts index 279fa054..c29f6bf6 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -1,85 +1,81 @@ -import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; +import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; const sidebars: SidebarsConfig = { docs: [ - 'index', + "index", { - type: 'category', - label: 'Get Running', + type: "category", + label: "Get Running", collapsed: false, - items: [ - 'getting-started', - 'cli-reference', - 'reading-output', - ], + items: ["getting-started", "cli-reference", "reading-output"], }, { - type: 'category', - label: 'Fix Issues', + type: "category", + label: "Fix Issues", collapsed: true, items: [ - 'how-remediation-works', - 'remediation-strategy', - 'fix-mode', - 'html-report', + "how-remediation-works", + "remediation-strategy", + "fix-mode", + "html-report", ], }, { - type: 'category', - label: 'Integrate', + type: "category", + label: "Integrate", collapsed: true, items: [ - 'workflow-integration', - 'ratcheting', - 'ai-assistant-integration', - 'sarif', - 'cyclonedx', - 'caching', - 'offline-advisory-db', - 'offline-vs-online-results', - 'corporate-proxy', + "workflow-integration", + "ratcheting", + "ai-assistant-integration", + "sarif", + "cyclonedx", + "caching", + "offline-advisory-db", + "offline-vs-online-results", + "corporate-proxy", ], }, { - type: 'category', - label: 'Reference', + type: "category", + label: "Reference", collapsed: true, items: [ - 'parser-coverage', - 'how-it-works', - 'comparison', - 'troubleshooting', - 'security-assurance-case', - 'roadmap', - 'press', + "parser-coverage", + "how-it-works", + "comparison", + "troubleshooting", + "security-assurance-case", + "roadmap", + "press", ], }, { - type: 'category', - label: 'Case Studies', - link: { type: 'doc', id: 'case-studies/index' }, + type: "category", + label: "Case Studies", + link: { type: "doc", id: "case-studies/index" }, items: [ - 'case-studies/owasp-juice-shop', - 'case-studies/nestjs', - 'case-studies/analog', - 'case-studies/lint-staged', - 'case-studies/ghost', - 'case-studies/astro', - 'case-studies/turborepo', - 'case-studies/vscode', - 'case-studies/gatsby', - 'case-studies/vercel-ai-sdk', - 'case-studies/mastra', - 'case-studies/lit', - 'case-studies/langchainjs', - 'case-studies/openai-agents-js', - 'case-studies/n8n', - 'case-studies/camofox-browser', - 'case-studies/payload', - 'case-studies/presenton', - 'case-studies/storybook', - 'case-studies/strapi', - 'case-studies/twenty', + "case-studies/owasp-juice-shop", + "case-studies/nestjs", + "case-studies/analog", + "case-studies/lint-staged", + "case-studies/ghost", + "case-studies/astro", + "case-studies/turborepo", + "case-studies/vscode", + "case-studies/gatsby", + "case-studies/vercel-ai-sdk", + "case-studies/mastra", + "case-studies/lit", + "case-studies/langchainjs", + "case-studies/openai-agents-js", + "case-studies/n8n", + "case-studies/camofox-browser", + "case-studies/payload", + "case-studies/presenton", + "case-studies/storybook", + "case-studies/strapi", + "case-studies/twenty", ], }, ], diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 7ef240d2..d517c2e9 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap"); :root { --ifm-color-primary: #47d1ff; @@ -11,8 +11,8 @@ --ifm-background-color: transparent; --ifm-navbar-background-color: rgba(8, 17, 31, 0.94); --ifm-footer-background-color: #07101d; - --ifm-font-family-base: 'Space Grotesk', sans-serif; - --ifm-font-family-monospace: 'IBM Plex Mono', monospace; + --ifm-font-family-base: "Space Grotesk", sans-serif; + --ifm-font-family-monospace: "IBM Plex Mono", monospace; --ifm-code-font-size: 92%; --ifm-card-border-radius: 1rem; --cve-bg: #08111f; @@ -30,7 +30,7 @@ } html, -html[data-theme='dark'] { +html[data-theme="dark"] { --ifm-background-color: transparent; --ifm-background-surface-color: transparent; --ifm-color-content: var(--cve-text); @@ -64,12 +64,12 @@ body { .docs-wrapper, .docMainContainer, .theme-doc-root, -[class*='mainWrapper'], -[class*='docsWrapper'], -[class*='docRoot'], -[class*='docPage'], -[class*='docMainContainer'], -[class*='docItemContainer'] { +[class*="mainWrapper"], +[class*="docsWrapper"], +[class*="docRoot"], +[class*="docPage"], +[class*="docMainContainer"], +[class*="docItemContainer"] { background: transparent !important; } @@ -116,7 +116,11 @@ body { padding: 0 0.75rem 0 0; border: 1px solid rgba(183, 212, 255, 0.22); border-radius: 0.95rem; - background: linear-gradient(95deg, rgba(8, 22, 44, 0.9), rgba(6, 20, 41, 0.76)); + background: linear-gradient( + 95deg, + rgba(8, 22, 44, 0.9), + rgba(6, 20, 41, 0.76) + ); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28); } @@ -132,7 +136,12 @@ body { margin-right: 1.25rem; padding: 0 0.9rem; border-radius: 0.85rem 0 0 0.85rem; - background: linear-gradient(90deg, #ffffff 0%, #f5f9ff 74%, rgba(245, 249, 255, 0.86) 100%); + background: linear-gradient( + 90deg, + #ffffff 0%, + #f5f9ff 74%, + rgba(245, 249, 255, 0.86) 100% + ); } .navbar__logo { @@ -242,7 +251,7 @@ body { .navbar-icon-link .iconExternalLink_nPIU, .navbar-owasp-link .iconExternalLink_nPIU, .navbar-github-link .iconExternalLink_nPIU, -.navbar-icon-link svg[class*='iconExternalLink'] { +.navbar-icon-link svg[class*="iconExternalLink"] { display: none; } @@ -260,11 +269,11 @@ body { color: #b7c9df; } -[class*='searchHintContainer'] { +[class*="searchHintContainer"] { right: 0.55rem; } -[class*='searchHint_'] { +[class*="searchHint_"] { min-width: 1.25rem; height: 1.25rem; border-color: rgba(183, 212, 255, 0.34); @@ -281,7 +290,12 @@ body { border: 1px solid rgba(183, 212, 255, 0.2); border-radius: 1rem; background: - linear-gradient(115deg, rgba(8, 22, 44, 0.94), rgba(15, 34, 58, 0.86) 48%, rgba(28, 70, 58, 0.74)), + linear-gradient( + 115deg, + rgba(8, 22, 44, 0.94), + rgba(15, 34, 58, 0.86) 48%, + rgba(28, 70, 58, 0.74) + ), rgba(8, 17, 31, 0.92); box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.18); } @@ -293,7 +307,13 @@ body { height: 2px; margin: -0.85rem auto 1.65rem; border-radius: 999px; - background: linear-gradient(90deg, transparent, var(--cve-primary), var(--cve-accent), transparent); + background: linear-gradient( + 90deg, + transparent, + var(--cve-primary), + var(--cve-accent), + transparent + ); opacity: 0.75; } @@ -329,7 +349,9 @@ body { font-weight: 500; text-decoration: none; opacity: 0.82; - transition: color 0.16s ease, opacity 0.16s ease; + transition: + color 0.16s ease, + opacity 0.16s ease; } .footer__link-item:hover { @@ -448,7 +470,9 @@ body { border-radius: 0.72rem; font-weight: 700; text-decoration: none; - transition: transform 0.2s ease, box-shadow 0.2s ease; + transition: + transform 0.2s ease, + box-shadow 0.2s ease; } .cve-button:hover { @@ -559,7 +583,10 @@ body { cursor: pointer; font-size: 0.78rem; font-weight: 600; - transition: color 0.15s, border-color 0.15s, background 0.15s; + transition: + color 0.15s, + border-color 0.15s, + background 0.15s; } .preview-tab:hover { @@ -641,7 +668,9 @@ body { max-width: 9.5rem; object-fit: contain; opacity: 0.82; - transition: opacity 0.2s ease, transform 0.2s ease; + transition: + opacity 0.2s ease, + transform 0.2s ease; } .press-bar-logo--cso { @@ -678,7 +707,6 @@ body { opacity: 1; } - .press-bar-logo-link--badge:hover { background: #fff; } diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx index f9c89f7a..8f5c6933 100644 --- a/website/src/pages/index.tsx +++ b/website/src/pages/index.tsx @@ -1,7 +1,7 @@ -import {useState, type ReactNode} from 'react'; -import Link from '@docusaurus/Link'; -import Layout from '@theme/Layout'; -import CodeBlock from '@theme/CodeBlock'; +import { useState, type ReactNode } from "react"; +import Link from "@docusaurus/Link"; +import Layout from "@theme/Layout"; +import CodeBlock from "@theme/CodeBlock"; function CommandCard({ title, @@ -33,79 +33,95 @@ function FeatureCard({ ); } -const PRESS_OUTLETS: {label: string; href: string; src?: string; logoClass?: string; badge?: boolean}[] = [ +const PRESS_OUTLETS: { + label: string; + href: string; + src?: string; + logoClass?: string; + badge?: boolean; +}[] = [ { - label: 'SD Times', - href: 'https://sdtimes.com/security/shift-left-how-cve-lite-cli-is-transforming-developer-security/', - src: 'img/press/sdtimes.png', - logoClass: 'press-bar-logo--sdtimes', + label: "SD Times", + href: "https://sdtimes.com/security/shift-left-how-cve-lite-cli-is-transforming-developer-security/", + src: "img/press/sdtimes.png", + logoClass: "press-bar-logo--sdtimes", badge: true, }, { - label: 'SecurityWeek', - href: 'https://www.securityweek.com/owasp-incubator-project-helps-developers-find-and-fix-vulnerable-dependencies-in-seconds/', - src: 'img/press/securityweek.webp', - logoClass: 'press-bar-logo--securityweek', + label: "SecurityWeek", + href: "https://www.securityweek.com/owasp-incubator-project-helps-developers-find-and-fix-vulnerable-dependencies-in-seconds/", + src: "img/press/securityweek.webp", + logoClass: "press-bar-logo--securityweek", }, { - label: 'CSO Online', - href: 'https://www.csoonline.com/article/4176701/as-ai-speeds-coding-cve-lite-cli-keeps-security-deliberately-ai-free.html', - src: 'img/press-cso.svg', - logoClass: 'press-bar-logo--cso', + label: "CSO Online", + href: "https://www.csoonline.com/article/4176701/as-ai-speeds-coding-cve-lite-cli-keeps-security-deliberately-ai-free.html", + src: "img/press-cso.svg", + logoClass: "press-bar-logo--cso", }, { - label: 'Help Net Security', - href: 'https://www.helpnetsecurity.com/2026/05/20/cve-lite-cli-open-source-dependency-vulnerability-scanner/', - src: 'img/press-helpnetsecurity.svg', - logoClass: 'press-bar-logo--helpnet', + label: "Help Net Security", + href: "https://www.helpnetsecurity.com/2026/05/20/cve-lite-cli-open-source-dependency-vulnerability-scanner/", + src: "img/press-helpnetsecurity.svg", + logoClass: "press-bar-logo--helpnet", }, { - label: 'ReversingLabs', - href: 'https://www.reversinglabs.com/blog/cve-lite-cli', + label: "ReversingLabs", + href: "https://www.reversinglabs.com/blog/cve-lite-cli", }, { - label: 'DevOps.com', - href: 'https://devops.com/owasp-adopts-cve-lite-cli-to-boost-dependency-scanning/', + label: "DevOps.com", + href: "https://devops.com/owasp-adopts-cve-lite-cli-to-boost-dependency-scanning/", }, ]; function ScreenshotPreview(): ReactNode { - const [activePreview, setActivePreview] = useState<'terminal' | 'report' | 'verbose'>('report'); + const [activePreview, setActivePreview] = useState< + "terminal" | "report" | "verbose" + >("report"); const previews = { terminal: { - label: 'Terminal', - image: 'img/default-output.png', - alt: 'CVE Lite CLI terminal output', + label: "Terminal", + image: "img/default-output.png", + alt: "CVE Lite CLI terminal output", }, report: { - label: 'HTML Report', - image: 'img/html-report-dashboard.png', - alt: 'CVE Lite CLI HTML report dashboard', + label: "HTML Report", + image: "img/html-report-dashboard.png", + alt: "CVE Lite CLI HTML report dashboard", }, verbose: { - label: 'Verbose', - image: 'img/verbose-output-1.png', - alt: 'CVE Lite CLI verbose fix plan output', + label: "Verbose", + image: "img/verbose-output-1.png", + alt: "CVE Lite CLI verbose fix plan output", }, }; return (
    -
    +
    {Object.entries(previews).map(([key, preview]) => ( ))}
    - {previews[activePreview].alt} + {previews[activePreview].alt}
    ); @@ -115,30 +131,43 @@ export default function Home(): ReactNode { return ( + description="CVE Lite CLI scans JS/TS lockfiles locally, explains direct and transitive dependency risk, and provides parent-aware remediation guidance." + >

    - JavaScript/TypeScript Dependency Scanner —{' '} - + JavaScript/TypeScript Dependency Scanner —{" "} + An OWASP Foundation Project

    Scan. Understand. Fix.

    - Most security tools are built around pipelines, not developers. CVE - Lite CLI scans your lockfile locally in seconds, explains the + Most security tools are built around pipelines, not developers. + CVE Lite CLI scans your lockfile locally in seconds, explains the dependency path, and tells you what to update before you push.

    - + View on GitHub - + View on npm - + GitHub Action
    @@ -181,14 +210,29 @@ export default function Home(): ReactNode { key={outlet.href} to={outlet.href} aria-label={`${outlet.label} article`} - className={outlet.src ? `press-bar-logo-link${outlet.badge ? ' press-bar-logo-link--badge' : ''}` : 'press-bar-text-link'}> + className={ + outlet.src + ? `press-bar-logo-link${outlet.badge ? " press-bar-logo-link--badge" : ""}` + : "press-bar-text-link" + } + > {outlet.src ? ( - {outlet.label} - ) : outlet.label} + {outlet.label} + ) : ( + outlet.label + )} ))}
    - View all press → + + View all press → +
    @@ -215,7 +259,9 @@ export default function Home(): ReactNode {

    Parent-aware remediation

    -

    Fix the package that controls the vulnerable dependency path.

    +

    + Fix the package that controls the vulnerable dependency path. +

    Transitive CVEs are easy to mishandle. CVE Lite CLI avoids recommending direct installs for packages that are only present @@ -224,16 +270,26 @@ export default function Home(): ReactNode {

    - Avoid for transitive-only packages - npm install vulnerable-child@fixed + + Avoid for transitive-only packages + + + npm install vulnerable-child@fixed +
    - Prefer when the range allows it + + Prefer when the range allows it + npm update parent-package
    - Use when the range must change - npm install parent-package@target + + Use when the range must change + + + npm install parent-package@target +
    @@ -246,7 +302,9 @@ export default function Home(): ReactNode { packages can still map back to their logical parent chain. - Read the remediation strategy{' '} + + Read the remediation strategy + {" "} to see when the CLI recommends direct upgrades, parent updates, or parent upgrades. @@ -266,17 +324,17 @@ export default function Home(): ReactNode {
    Generate a self-contained dashboard with severity cards, - searchable findings, and copy-ready fix commands.{' '} + searchable findings, and copy-ready fix commands.{" "} Read the guide. Sync OSV data locally and scan restricted environments without - runtime advisory API calls.{' '} + runtime advisory API calls.{" "} Read the guide. See how CVE Lite CLI compares with Dependabot, npm audit, - OSV-Scanner, Snyk, and Socket.{' '} + OSV-Scanner, Snyk, and Socket.{" "} Compare tools.
    diff --git a/website/src/theme/Root.tsx b/website/src/theme/Root.tsx index d42db15b..13a91824 100644 --- a/website/src/theme/Root.tsx +++ b/website/src/theme/Root.tsx @@ -1,6 +1,6 @@ -import type {ReactNode} from 'react'; +import type { ReactNode } from "react"; -export default function Root({children}: {children: ReactNode}): ReactNode { +export default function Root({ children }: { children: ReactNode }): ReactNode { return ( <>
    diff --git a/website/static/img/OWASP_Logo_Black_TM.png b/website/static/img/OWASP_Logo_Black_TM.png index b3550abf..ee3c635a 100644 Binary files a/website/static/img/OWASP_Logo_Black_TM.png and b/website/static/img/OWASP_Logo_Black_TM.png differ diff --git a/website/static/img/camofox-browser-logo.png b/website/static/img/camofox-browser-logo.png index 6496b061..fd0c9265 100644 Binary files a/website/static/img/camofox-browser-logo.png and b/website/static/img/camofox-browser-logo.png differ diff --git a/website/static/img/default-output.png b/website/static/img/default-output.png index 2cba2d19..14e6f05b 100644 Binary files a/website/static/img/default-output.png and b/website/static/img/default-output.png differ diff --git a/website/static/img/favicon.png b/website/static/img/favicon.png index 62b0817d..b01e0e2c 100644 Binary files a/website/static/img/favicon.png and b/website/static/img/favicon.png differ diff --git a/website/static/img/gatsby-logo.svg b/website/static/img/gatsby-logo.svg index 5578b06c..a965e771 100644 --- a/website/static/img/gatsby-logo.svg +++ b/website/static/img/gatsby-logo.svg @@ -1,7 +1,3 @@ - - + Gatsby - - - - + \ No newline at end of file diff --git a/website/static/img/github-mark.svg b/website/static/img/github-mark.svg index 8efdeb54..3151061c 100644 --- a/website/static/img/github-mark.svg +++ b/website/static/img/github-mark.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/website/static/img/html-report-dashboard.png b/website/static/img/html-report-dashboard.png index d59b5fff..9f8b23a7 100644 Binary files a/website/static/img/html-report-dashboard.png and b/website/static/img/html-report-dashboard.png differ diff --git a/website/static/img/langchainjs-logo.svg b/website/static/img/langchainjs-logo.svg index ba0aa4cc..d16ae220 100644 --- a/website/static/img/langchainjs-logo.svg +++ b/website/static/img/langchainjs-logo.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/website/static/img/lit-logo.svg b/website/static/img/lit-logo.svg index 0771dd03..acfda06e 100644 --- a/website/static/img/lit-logo.svg +++ b/website/static/img/lit-logo.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/website/static/img/logo-with-title-removebg-preview.png b/website/static/img/logo-with-title-removebg-preview.png index 651a94de..7512d021 100644 Binary files a/website/static/img/logo-with-title-removebg-preview.png and b/website/static/img/logo-with-title-removebg-preview.png differ diff --git a/website/static/img/logos-combined.svg b/website/static/img/logos-combined.svg index 4851d4e2..653397f5 100644 --- a/website/static/img/logos-combined.svg +++ b/website/static/img/logos-combined.svg @@ -1,17 +1 @@ - - - - - - - - - + - - \ No newline at end of file ++ \ No newline at end of file diff --git a/website/static/img/mastra-logo.svg b/website/static/img/mastra-logo.svg index f12ce9fe..99967e34 100644 --- a/website/static/img/mastra-logo.svg +++ b/website/static/img/mastra-logo.svg @@ -1,17 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/website/static/img/n8n-logo.png b/website/static/img/n8n-logo.png index 2bb6b2d7..e88865d8 100644 Binary files a/website/static/img/n8n-logo.png and b/website/static/img/n8n-logo.png differ diff --git a/website/static/img/npm-mark.svg b/website/static/img/npm-mark.svg index a2406cbc..397953c7 100644 --- a/website/static/img/npm-mark.svg +++ b/website/static/img/npm-mark.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/website/static/img/openai-agents-js-logo.svg b/website/static/img/openai-agents-js-logo.svg index d0bed40b..a8533c45 100644 --- a/website/static/img/openai-agents-js-logo.svg +++ b/website/static/img/openai-agents-js-logo.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/website/static/img/payload-logo.svg b/website/static/img/payload-logo.svg index 35b4cc48..e4d645c4 100644 --- a/website/static/img/payload-logo.svg +++ b/website/static/img/payload-logo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/website/static/img/presenton-logo.png b/website/static/img/presenton-logo.png index e50ca497..937382c7 100644 Binary files a/website/static/img/presenton-logo.png and b/website/static/img/presenton-logo.png differ diff --git a/website/static/img/press-cso.svg b/website/static/img/press-cso.svg index 86a788b6..81dfaad2 100644 --- a/website/static/img/press-cso.svg +++ b/website/static/img/press-cso.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/website/static/img/press-developmentcurated.svg b/website/static/img/press-developmentcurated.svg index 478fab7e..65585ebe 100644 --- a/website/static/img/press-developmentcurated.svg +++ b/website/static/img/press-developmentcurated.svg @@ -1,7 +1 @@ - - DEVELOPMENT - - - - - +DEVELOPMENT \ No newline at end of file diff --git a/website/static/img/press-helpnetsecurity.svg b/website/static/img/press-helpnetsecurity.svg index 79aa02e6..f544bd12 100644 --- a/website/static/img/press-helpnetsecurity.svg +++ b/website/static/img/press-helpnetsecurity.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/website/static/img/press/medium-wordmark.png b/website/static/img/press/medium-wordmark.png index d9283a3c..826ba721 100644 Binary files a/website/static/img/press/medium-wordmark.png and b/website/static/img/press/medium-wordmark.png differ diff --git a/website/static/img/press/sdtimes.png b/website/static/img/press/sdtimes.png index 1a7adcef..418b1d39 100644 Binary files a/website/static/img/press/sdtimes.png and b/website/static/img/press/sdtimes.png differ diff --git a/website/static/img/storybook-logo.svg b/website/static/img/storybook-logo.svg index a47acc04..1df05803 100644 --- a/website/static/img/storybook-logo.svg +++ b/website/static/img/storybook-logo.svg @@ -1 +1 @@ -logo-storybook-inverse \ No newline at end of file +logo-storybook-inverse \ No newline at end of file diff --git a/website/static/img/strapi-logo.svg b/website/static/img/strapi-logo.svg index 83aaf967..a081aef5 100644 --- a/website/static/img/strapi-logo.svg +++ b/website/static/img/strapi-logo.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/website/static/img/twenty-logo.svg b/website/static/img/twenty-logo.svg index bad18fab..85eec121 100644 --- a/website/static/img/twenty-logo.svg +++ b/website/static/img/twenty-logo.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/website/static/img/verbose-output-1.png b/website/static/img/verbose-output-1.png index cf61f5d0..f78d322b 100644 Binary files a/website/static/img/verbose-output-1.png and b/website/static/img/verbose-output-1.png differ diff --git a/website/static/img/verbose-output-2.png b/website/static/img/verbose-output-2.png index d17b12d5..4781e37d 100644 Binary files a/website/static/img/verbose-output-2.png and b/website/static/img/verbose-output-2.png differ diff --git a/website/static/img/verbose-output-3.png b/website/static/img/verbose-output-3.png index f0f9fff3..15a03333 100644 Binary files a/website/static/img/verbose-output-3.png and b/website/static/img/verbose-output-3.png differ diff --git a/website/static/img/vercel-ai-sdk-logo.svg b/website/static/img/vercel-ai-sdk-logo.svg index eb67ec48..a8f8e04f 100644 --- a/website/static/img/vercel-ai-sdk-logo.svg +++ b/website/static/img/vercel-ai-sdk-logo.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/website/static/img/vscode-logo.png b/website/static/img/vscode-logo.png index 4fd470bf..c770a7b3 100644 Binary files a/website/static/img/vscode-logo.png and b/website/static/img/vscode-logo.png differ