From fb25b7101c55e62e7fa4ac874f4e46232a87c1a1 Mon Sep 17 00:00:00 2001 From: titan-ron <30556071+titan-ron@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:44:23 +0300 Subject: [PATCH 1/6] ci: add security scanning and test coverage for the public repo - CodeQL workflow (javascript-typescript + actions, weekly schedule) - dependency-review job blocking PRs that add vulnerable dependencies - CODEOWNERS so the ruleset's code-owner review requirement is enforced - SECURITY.md pointing reporters at private vulnerability reporting - dependabot npm ecosystem (weekly, minor/patch grouped) - vitest v8 coverage: test:coverage script, CI step summary, report artifact; vitest.workspace.ts migrated to vitest.config.ts (workspace file is deprecated in vitest 3.2, and coverage config lives at the root) --- .github/CODEOWNERS | 3 + .github/dependabot.yml | 13 +- .github/workflows/ci.yml | 35 +- .github/workflows/codeql.yml | 42 ++ .gitignore | 1 + AGENTS.md | 1 + SECURITY.md | 24 ++ package-lock.json | 752 ++++++++++++++++++++++++++++++++++- package.json | 2 + vitest.config.ts | 35 ++ vitest.workspace.ts | 25 -- 11 files changed, 902 insertions(+), 31 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/codeql.yml create mode 100644 SECURITY.md create mode 100644 vitest.config.ts delete mode 100644 vitest.workspace.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..33099a5e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Every change routes review to the maintainers; any one of them satisfies +# the code-owner review requirement in the branch ruleset. +* @titan-ron @matansocher @amitbreuer diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 091966b1..5cdbdcb9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,18 @@ -# Proposes reviewed updates for the release-tag-pinned actions in .github/workflows. +# Proposes reviewed updates for the release-tag-pinned actions in .github/workflows +# and for npm dependencies (minor/patch grouped into a single weekly PR). version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly + + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + groups: + npm-minor-patch: + update-types: + - minor + - patch diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4eb5d884..c4c92d85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,11 +36,22 @@ jobs: - name: ๐Ÿ”Ž Typecheck run: npm run typecheck - - name: ๐Ÿงช Unit tests - run: npm run test:unit + - name: ๐Ÿงช Unit + component tests (with coverage) + run: npm run test:coverage - - name: ๐Ÿงฉ Component tests - run: npm run test:component + - name: ๐Ÿ“Š Coverage summary + run: | + jq -r '.total + | "## Coverage\n\n| Metric | % | Covered / Total |\n| --- | --- | --- |\n| Lines | \(.lines.pct)% | \(.lines.covered) / \(.lines.total) |\n| Statements | \(.statements.pct)% | \(.statements.covered) / \(.statements.total) |\n| Functions | \(.functions.pct)% | \(.functions.covered) / \(.functions.total) |\n| Branches | \(.branches.pct)% | \(.branches.covered) / \(.branches.total) |"' \ + coverage/coverage-summary.json >> "$GITHUB_STEP_SUMMARY" + + - name: Upload coverage report + uses: actions/upload-artifact@v7.0.1 + with: + name: coverage-report + path: coverage/ + if-no-files-found: ignore + retention-days: 14 - name: Build run: npm run build @@ -59,3 +70,19 @@ jobs: test-results/ if-no-files-found: ignore retention-days: 14 + + # Blocks PRs that introduce dependencies with known vulnerabilities. + dependency-review: + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: 'read' + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + + - name: ๐Ÿ›ก๏ธ Dependency review + uses: actions/dependency-review-action@v5.0.0 + with: + fail-on-severity: high diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..16386e0b --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,42 @@ +name: 'CodeQL' + +on: + workflow_dispatch: + pull_request: + push: + branches: [main] + schedule: + # Weekly sweep so query-pack updates re-scan main even without pushes. + - cron: '32 6 * * 1' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + analyze: + name: analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + security-events: 'write' # to upload SARIF results + contents: 'read' + actions: 'read' + strategy: + fail-fast: false + matrix: + language: [javascript-typescript, actions] + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4.37.6 + with: + languages: ${{ matrix.language }} + build-mode: none + + - name: ๐Ÿ”ฌ Analyze + uses: github/codeql-action/analyze@v4.37.6 + with: + category: '/language:${{ matrix.language }}' diff --git a/.gitignore b/.gitignore index 84a5b49f..803045e7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist/ # Test output test-results/ playwright-report/ +coverage/ # Editor / local tooling .idea/ diff --git a/AGENTS.md b/AGENTS.md index 88b0b3c0..095e10de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,7 @@ This file provides guidance to AI coding agents (Claude Code, Codex, GitHub Copi - `npm run typecheck` โ€” `tsc --noEmit` (there is no linter; this is the static gate) - `npm test` โ€” all vitest tests (unit + component tiers) - `npm run test:unit` / `npm run test:component` โ€” one tier +- `npm run test:coverage` โ€” unit + component with a v8 coverage report in `coverage/` - `npm run test:e2e` โ€” Playwright E2E against the built app (run `npm run build` first) - `npx vitest run tests/indexer.test.ts` โ€” one test file - `npx vitest run -t "pattern"` โ€” tests matching a name diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..50b35db7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,24 @@ +# Security Policy + +## Supported versions + +Cockpit is pre-1.0; only the latest state of `main` receives security fixes. + +## Reporting a vulnerability + +Please do not report security vulnerabilities through public GitHub issues. + +Instead, use GitHub's private vulnerability reporting: go to the +[Security tab](https://github.com/tashtit/cockpit/security) and click +**Report a vulnerability**. You should receive a response within a few days. + +Please include enough detail to reproduce the issue: affected component +(main process, preload bridge, renderer, or a provider parser), steps to +reproduce, and impact as you understand it. + +## Scope notes + +Cockpit's renderer is sandboxed and all renderer input crossing IPC is +treated as untrusted. Reports about paths that bypass `assertKnownRepoRoot` +or otherwise act on unvalidated renderer-supplied paths are particularly +relevant. diff --git a/package-lock.json b/package-lock.json index f9efbfb9..a0800fa0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^5.2.0", + "@vitest/coverage-v8": "^3.2.7", "electron": "^43.3.0", "electron-vite": "^5.0.0", "jsdom": "^29.1.1", @@ -42,6 +43,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "5.1.11", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", @@ -401,6 +416,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -1058,6 +1083,34 @@ } } }, + "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/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", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1125,6 +1178,17 @@ "node": "^22.20 || ^24.12 || >=25" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@playwright/test": { "version": "1.62.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", @@ -1776,6 +1840,40 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.7.tgz", + "integrity": "sha512-NEGWJS2XNu2PfRLQwOO3CTKj1tTETxNBdk454vDxVBhxJYhPaA/eS0nAI0c+1El1P7a60z8+i+ZrQoGESweGKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.7", + "vitest": "3.2.7" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", @@ -1897,7 +1995,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -1936,6 +2033,25 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -1946,6 +2062,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.11.13", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", @@ -1969,6 +2095,19 @@ "require-from-string": "^2.0.2" } }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/browserslist": { "version": "4.28.8", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", @@ -2111,6 +2250,26 @@ "node": ">= 16" } }, + "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/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -2128,6 +2287,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -2246,6 +2420,13 @@ "license": "MIT", "peer": true }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/electron": { "version": "43.3.0", "resolved": "https://registry.npmjs.org/electron/-/electron-43.3.0.tgz", @@ -2319,6 +2500,13 @@ "dev": true, "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", @@ -2470,6 +2658,23 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2494,6 +2699,61 @@ "node": ">=6.9.0" } }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2501,6 +2761,16 @@ "dev": true, "license": "ISC" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/hast-util-is-element": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", @@ -2592,6 +2862,13 @@ "node": "^20.19.0 || ^22.12.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/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -2652,6 +2929,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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/is-hexadecimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", @@ -2681,6 +2968,83 @@ "dev": true, "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-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": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2839,6 +3203,47 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -3699,6 +4104,32 @@ "node": ">=4" } }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3734,6 +4165,13 @@ "node": ">=18" } }, + "node_modules/package-json-from-dist": { + "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==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -3772,6 +4210,40 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "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==", + "dev": true, + "license": "ISC" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -4188,6 +4660,29 @@ "semver": "bin/semver.js" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -4195,6 +4690,19 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4229,6 +4737,60 @@ "dev": true, "license": "MIT" }, + "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==", + "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/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/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -4243,6 +4805,49 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -4307,6 +4912,19 @@ "node": ">= 8.0" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -4314,6 +4932,21 @@ "dev": true, "license": "MIT" }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5335,6 +5968,22 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -5352,6 +6001,107 @@ "node": ">=8" } }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/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/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", diff --git a/package.json b/package.json index c0d70607..30ab6758 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "start": "electron-vite preview", "typecheck": "tsc --noEmit -p tsconfig.json", "test": "vitest run", + "test:coverage": "vitest run --coverage", "test:unit": "vitest run --project unit", "test:component": "vitest run --project component", "test:e2e": "playwright test" @@ -29,6 +30,7 @@ "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^5.2.0", + "@vitest/coverage-v8": "^3.2.7", "electron": "^43.3.0", "electron-vite": "^5.0.0", "jsdom": "^29.1.1", diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..80d58432 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,35 @@ +import { defineConfig } from 'vitest/config' + +/** + * Two test tiers, one runner: + * - unit: main-process logic against real files in tmpdirs (node env) + * - component: renderer components against a stubbed window.cockpit (jsdom env) + * E2E lives in tests/e2e and runs under Playwright, not Vitest. + */ +export default defineConfig({ + test: { + projects: [ + { + test: { + name: 'unit', + environment: 'node', + include: ['tests/*.test.ts'] + } + }, + { + test: { + name: 'component', + environment: 'jsdom', + include: ['tests/component/**/*.test.{ts,tsx}'], + setupFiles: ['tests/component/setup.ts'] + } + } + ], + coverage: { + provider: 'v8', + include: ['src/**'], + reporter: ['text-summary', 'html', 'json-summary'], + reportsDirectory: 'coverage' + } + } +}) diff --git a/vitest.workspace.ts b/vitest.workspace.ts deleted file mode 100644 index 57215970..00000000 --- a/vitest.workspace.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineWorkspace } from 'vitest/config' - -/** - * Two test tiers, one runner: - * - unit: main-process logic against real files in tmpdirs (node env) - * - component: renderer components against a stubbed window.cockpit (jsdom env) - * E2E lives in tests/e2e and runs under Playwright, not Vitest. - */ -export default defineWorkspace([ - { - test: { - name: 'unit', - environment: 'node', - include: ['tests/*.test.ts'] - } - }, - { - test: { - name: 'component', - environment: 'jsdom', - include: ['tests/component/**/*.test.{ts,tsx}'], - setupFiles: ['tests/component/setup.ts'] - } - } -]) From fe7d57ac9a3c1663134412010491adc877c500dc Mon Sep 17 00:00:00 2001 From: titan-ron <30556071+titan-ron@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:56:30 +0300 Subject: [PATCH 2/6] ci: upload coverage to github's native code coverage api Replaces the coverage-report artifact with actions/upload-code-coverage, which feeds GitHub's built-in coverage UI; vitest now also emits a cobertura report for it. --- .github/workflows/ci.yml | 15 +++++++++------ vitest.config.ts | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4c92d85..173d11b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,8 @@ jobs: timeout-minutes: 15 permissions: contents: 'read' # for actions/checkout + code-quality: 'write' # for actions/upload-code-coverage + pull-requests: 'read' # upload-code-coverage looks up the PR for pushes to main steps: - name: Checkout uses: actions/checkout@v7.0.1 @@ -45,13 +47,14 @@ jobs: | "## Coverage\n\n| Metric | % | Covered / Total |\n| --- | --- | --- |\n| Lines | \(.lines.pct)% | \(.lines.covered) / \(.lines.total) |\n| Statements | \(.statements.pct)% | \(.statements.covered) / \(.statements.total) |\n| Functions | \(.functions.pct)% | \(.functions.covered) / \(.functions.total) |\n| Branches | \(.branches.pct)% | \(.branches.covered) / \(.branches.total) |"' \ coverage/coverage-summary.json >> "$GITHUB_STEP_SUMMARY" - - name: Upload coverage report - uses: actions/upload-artifact@v7.0.1 + - name: โฌ†๏ธ Upload coverage to GitHub + # Fork PRs run with a read-only token, so the coverage API is unavailable there. + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: actions/upload-code-coverage@v1.4.2 with: - name: coverage-report - path: coverage/ - if-no-files-found: ignore - retention-days: 14 + file: coverage/cobertura-coverage.xml + language: TypeScript + label: code-coverage/vitest - name: Build run: npm run build diff --git a/vitest.config.ts b/vitest.config.ts index 80d58432..8c825a99 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -28,7 +28,7 @@ export default defineConfig({ coverage: { provider: 'v8', include: ['src/**'], - reporter: ['text-summary', 'html', 'json-summary'], + reporter: ['text-summary', 'html', 'json-summary', 'cobertura'], reportsDirectory: 'coverage' } } From 6bfb50c7a56b6763c52349c99c58b16bda79474d Mon Sep 17 00:00:00 2001 From: titan-ron <30556071+titan-ron@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:59:29 +0300 Subject: [PATCH 3/6] ci: make coverage upload non-blocking until code quality is enabled --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 173d11b3..a6ff1845 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,9 @@ jobs: file: coverage/cobertura-coverage.xml language: TypeScript label: code-coverage/vitest + # Non-blocking until "Code quality" is enabled in repo settings + # (Settings โ†’ Security โ†’ Code quality); drop this once it is. + fail-on-error: false - name: Build run: npm run build From b90cb701a4fd787d2b3a0a8887240890e31d8682 Mon Sep 17 00:00:00 2001 From: titan-ron <30556071+titan-ron@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:10:00 +0300 Subject: [PATCH 4/6] ci: split coverage per test tier, drop codeql workflow for default setup - unit and component tests run separately again, each with coverage scoped to the layer it exercises, uploaded under its own label - coverage upload is blocking again (fail-on-error default) - step-summary coverage table removed; GitHub's coverage UI replaces it - codeql.yml removed in favor of GitHub's built-in default setup - CODEOWNERS narrowed to @titan-ron --- .github/CODEOWNERS | 5 ++--- .github/workflows/ci.yml | 28 +++++++++++++----------- .github/workflows/codeql.yml | 42 ------------------------------------ AGENTS.md | 3 ++- package.json | 2 ++ vitest.config.ts | 2 +- 6 files changed, 22 insertions(+), 60 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 33099a5e..e685cd9e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,2 @@ -# Every change routes review to the maintainers; any one of them satisfies -# the code-owner review requirement in the branch ruleset. -* @titan-ron @matansocher @amitbreuer +# Every change routes review here; satisfies the ruleset's code-owner review rule. +* @titan-ron diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6ff1845..34eb642e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,26 +38,28 @@ jobs: - name: ๐Ÿ”Ž Typecheck run: npm run typecheck - - name: ๐Ÿงช Unit + component tests (with coverage) - run: npm run test:coverage + - name: ๐Ÿงช Unit tests (with coverage) + run: npm run test:coverage:unit - - name: ๐Ÿ“Š Coverage summary - run: | - jq -r '.total - | "## Coverage\n\n| Metric | % | Covered / Total |\n| --- | --- | --- |\n| Lines | \(.lines.pct)% | \(.lines.covered) / \(.lines.total) |\n| Statements | \(.statements.pct)% | \(.statements.covered) / \(.statements.total) |\n| Functions | \(.functions.pct)% | \(.functions.covered) / \(.functions.total) |\n| Branches | \(.branches.pct)% | \(.branches.covered) / \(.branches.total) |"' \ - coverage/coverage-summary.json >> "$GITHUB_STEP_SUMMARY" + - name: ๐Ÿงฉ Component tests (with coverage) + run: npm run test:coverage:component - - name: โฌ†๏ธ Upload coverage to GitHub + - name: โฌ†๏ธ Upload unit coverage # Fork PRs run with a read-only token, so the coverage API is unavailable there. if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} uses: actions/upload-code-coverage@v1.4.2 with: - file: coverage/cobertura-coverage.xml + file: coverage/unit/cobertura-coverage.xml language: TypeScript - label: code-coverage/vitest - # Non-blocking until "Code quality" is enabled in repo settings - # (Settings โ†’ Security โ†’ Code quality); drop this once it is. - fail-on-error: false + label: code-coverage/unit + + - name: โฌ†๏ธ Upload component coverage + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: actions/upload-code-coverage@v1.4.2 + with: + file: coverage/component/cobertura-coverage.xml + language: TypeScript + label: code-coverage/component - name: Build run: npm run build diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 16386e0b..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: 'CodeQL' - -on: - workflow_dispatch: - pull_request: - push: - branches: [main] - schedule: - # Weekly sweep so query-pack updates re-scan main even without pushes. - - cron: '32 6 * * 1' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - analyze: - name: analyze (${{ matrix.language }}) - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - security-events: 'write' # to upload SARIF results - contents: 'read' - actions: 'read' - strategy: - fail-fast: false - matrix: - language: [javascript-typescript, actions] - steps: - - name: Checkout - uses: actions/checkout@v7.0.1 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.6 - with: - languages: ${{ matrix.language }} - build-mode: none - - - name: ๐Ÿ”ฌ Analyze - uses: github/codeql-action/analyze@v4.37.6 - with: - category: '/language:${{ matrix.language }}' diff --git a/AGENTS.md b/AGENTS.md index 095e10de..166def46 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,8 @@ This file provides guidance to AI coding agents (Claude Code, Codex, GitHub Copi - `npm run typecheck` โ€” `tsc --noEmit` (there is no linter; this is the static gate) - `npm test` โ€” all vitest tests (unit + component tiers) - `npm run test:unit` / `npm run test:component` โ€” one tier -- `npm run test:coverage` โ€” unit + component with a v8 coverage report in `coverage/` +- `npm run test:coverage` โ€” unit + component with a combined v8 coverage report in `coverage/` +- `npm run test:coverage:unit` / `npm run test:coverage:component` โ€” one tier, scoped to the code it exercises (`coverage/unit`, `coverage/component`); these are what CI uploads - `npm run test:e2e` โ€” Playwright E2E against the built app (run `npm run build` first) - `npx vitest run tests/indexer.test.ts` โ€” one test file - `npx vitest run -t "pattern"` โ€” tests matching a name diff --git a/package.json b/package.json index 30ab6758..a7555efc 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "typecheck": "tsc --noEmit -p tsconfig.json", "test": "vitest run", "test:coverage": "vitest run --coverage", + "test:coverage:unit": "vitest run --project unit --coverage --coverage.reportsDirectory=coverage/unit --coverage.include='src/main/**' --coverage.include='src/preload/**' --coverage.include='src/shared/**'", + "test:coverage:component": "vitest run --project component --coverage --coverage.reportsDirectory=coverage/component --coverage.include='src/renderer/**'", "test:unit": "vitest run --project unit", "test:component": "vitest run --project component", "test:e2e": "playwright test" diff --git a/vitest.config.ts b/vitest.config.ts index 8c825a99..c9fd16ee 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -28,7 +28,7 @@ export default defineConfig({ coverage: { provider: 'v8', include: ['src/**'], - reporter: ['text-summary', 'html', 'json-summary', 'cobertura'], + reporter: ['text-summary', 'html', 'cobertura'], reportsDirectory: 'coverage' } } From 376fd10081e43b859eacbae28b8180c103227ff6 Mon Sep 17 00:00:00 2001 From: titan-ron <30556071+titan-ron@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:23:46 +0300 Subject: [PATCH 5/6] ci: fold dependency review into the ci job --- .github/workflows/ci.yml | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34eb642e..1058d76d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,14 @@ jobs: - name: Checkout uses: actions/checkout@v7.0.1 + # Blocks PRs that introduce dependencies with known vulnerabilities; + # diffs base..head, so it only applies to pull requests. + - name: ๐Ÿ›ก๏ธ Dependency review + if: ${{ github.event_name == 'pull_request' }} + uses: actions/dependency-review-action@v5.0.0 + with: + fail-on-severity: high + - name: Setup Node.js uses: actions/setup-node@v7.0.0 with: { node-version-file: '.nvmrc', cache: 'npm' } @@ -78,19 +86,3 @@ jobs: test-results/ if-no-files-found: ignore retention-days: 14 - - # Blocks PRs that introduce dependencies with known vulnerabilities. - dependency-review: - if: ${{ github.event_name == 'pull_request' }} - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: 'read' - steps: - - name: Checkout - uses: actions/checkout@v7.0.1 - - - name: ๐Ÿ›ก๏ธ Dependency review - uses: actions/dependency-review-action@v5.0.0 - with: - fail-on-severity: high From c7f247b56b739d92ddb9e65ce9b0cb7e48725857 Mon Sep 17 00:00:00 2001 From: titan-ron <30556071+titan-ron@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:28:27 +0300 Subject: [PATCH 6/6] ci: simplify fork gate on coverage uploads --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1058d76d..956ce005 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: - name: โฌ†๏ธ Upload unit coverage # Fork PRs run with a read-only token, so the coverage API is unavailable there. - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + if: ${{ !github.event.pull_request.head.repo.fork }} uses: actions/upload-code-coverage@v1.4.2 with: file: coverage/unit/cobertura-coverage.xml @@ -62,7 +62,7 @@ jobs: label: code-coverage/unit - name: โฌ†๏ธ Upload component coverage - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + if: ${{ !github.event.pull_request.head.repo.fork }} uses: actions/upload-code-coverage@v1.4.2 with: file: coverage/component/cobertura-coverage.xml