From d7a2024c3cb04dfbc449b34750960e41f0d3029e Mon Sep 17 00:00:00 2001 From: aksops Date: Fri, 1 May 2026 07:56:58 +0000 Subject: [PATCH 1/2] ci: SonarCloud quality gate + coverage on PR and main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a SonarCloud scan that ingests Go + JS/TS coverage on every PR and on push to main. Coverage is collected fresh in this workflow rather than reused from CI — the existing ci.yml runs go test without -coverprofile and adding it there would slow every PR build. Setup required (manual, one-time): 1. Sign in to sonarcloud.io with GitHub. 2. Import RandomCodeSpace/ctm; project key auto-generated as RandomCodeSpace_ctm (matches sonar-project.properties). 3. Generate a user token; add as repo secret SONAR_TOKEN. Until SONAR_TOKEN is set, the workflow runs the test+coverage steps and emits a workflow warning instead of erroring on the scan step. Coverage layout: - Go: go test -coverprofile=coverage.out (atomic mode for race-safe) - UI: vitest run --coverage with provider:v8 → ui/coverage/lcov.info - Both: excluded from git via .gitignore Excluded from analysis: dist/, vendor/, node_modules/, _attic/, .claude/, .codeiq/, internal/serve/dist/ (generated UI bundle), ui/playwright-report/, ui/test-results/, docs/. Quality gate uses SonarCloud's default ('clean as you code', 80% new-code coverage) — adjustable in the SonarCloud UI later. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/sonar.yml | 94 +++++++++++++ .gitignore | 4 + sonar-project.properties | 52 +++++++ ui/package.json | 1 + ui/pnpm-lock.yaml | 272 ++++++++++++++++++++++++++++++++++++ ui/vitest.config.ts | 15 ++ 6 files changed, 438 insertions(+) create mode 100644 .github/workflows/sonar.yml create mode 100644 sonar-project.properties diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 0000000..7de1f5c --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,94 @@ +# SonarCloud quality gate + coverage upload. +# +# Triggers: push to main (full-history scan) and PRs (incremental scan +# against the base branch). Concurrency-gated per-ref so reruns cancel +# in-flight scans. +# +# Coverage is collected fresh in this workflow rather than reused from +# CI — Sonar needs both go-coverprofile and lcov in the same workspace +# pass, and the existing CI job runs `go test` without -coverprofile. +# Adding coverage there would slow every PR build; keeping it here +# isolates the cost to the Sonar job. + +name: SonarCloud + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: read + +concurrency: + group: sonar-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Sonar uses git blame for new-code detection — shallow + # checkouts (default fetch-depth=1) attribute every line to + # the latest commit, breaking the quality-gate "new code" + # signal. fetch-depth: 0 grabs the full history. + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.33.0 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: ui/pnpm-lock.yaml + + - name: Build UI bundle + # Required before any Go test run: //go:embed all:dist in + # internal/serve/assets.go errors out if internal/serve/dist/ + # is empty. Same as ci.yml. + run: make ui + + - name: Go test with coverage + run: | + go test -tags sqlite_fts5 \ + -coverprofile=coverage.out \ + -covermode=atomic \ + ./... + + - name: UI install + run: pnpm -C ui install --frozen-lockfile + + - name: UI test with coverage + run: pnpm -C ui exec vitest run --coverage + + - name: Detect SONAR_TOKEN + id: token + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + if [ -n "$SONAR_TOKEN" ]; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "::warning::SONAR_TOKEN secret is not set; skipping SonarCloud scan. Add the token in repo Settings → Secrets and variables → Actions." + fi + + - name: SonarCloud Scan + if: steps.token.outputs.present == 'true' + uses: SonarSource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.gitignore b/.gitignore index af5d926..573cdb1 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,7 @@ docs/ # Local code-intelligence cache (codeiq) — runtime DB + neo4j store, not source .codeiq/ + +# Coverage reports (Go + vitest) — regenerated per-test, never committed +coverage.out +ui/coverage/ diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..bad3c5e --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,52 @@ +# SonarCloud project configuration. The scanner reads this file at the +# repo root automatically; the GitHub Action only needs SONAR_TOKEN. +# +# Project + organization keys must match what SonarCloud generated when +# the project was imported from GitHub — adjust if you renamed the org +# or the project (Sonar UI → Administration → General Settings → Project +# Key). + +sonar.projectKey=RandomCodeSpace_ctm +sonar.organization=randomcodespace +sonar.projectName=ctm + +# ── Sources ──────────────────────────────────────────────────────────── +# Scan everything under the repo root. Generated artifacts, vendored +# code, the embedded UI dist, and agent worktree scratch never enter +# the analysis — they're either machine-written or out of scope. +sonar.sources=. +sonar.exclusions=\ + **/dist/**,\ + **/node_modules/**,\ + **/vendor/**,\ + **/_attic/**,\ + **/.claude/**,\ + **/.codeiq/**,\ + internal/serve/dist/**,\ + ui/coverage/**,\ + ui/playwright-report/**,\ + ui/test-results/**,\ + coverage.out,\ + docs/** + +# ── Tests ────────────────────────────────────────────────────────────── +# Sonar separates "test code" from "production code" so coverage and +# duplication metrics target the right files. Playwright e2e specs +# stay out of the JS test set — they don't generate the unit-style +# coverage Sonar expects. +sonar.tests=. +sonar.test.inclusions=\ + **/*_test.go,\ + ui/src/**/*.test.ts,\ + ui/src/**/*.test.tsx +sonar.test.exclusions=ui/e2e/** + +# ── Coverage report paths ────────────────────────────────────────────── +# Go: `go test -coverprofile=coverage.out ./...` writes to repo root. +# JS/TS: vitest with provider:v8 writes lcov to ui/coverage/lcov.info +# (configured in ui/vitest.config.ts). +sonar.go.coverage.reportPaths=coverage.out +sonar.javascript.lcov.reportPaths=ui/coverage/lcov.info + +# ── General ──────────────────────────────────────────────────────────── +sonar.sourceEncoding=UTF-8 diff --git a/ui/package.json b/ui/package.json index 6519d4f..d1bf993 100644 --- a/ui/package.json +++ b/ui/package.json @@ -34,6 +34,7 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.2.0", + "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.0.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.12", diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index c344a48..e7eac08 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: '@vitejs/plugin-react': specifier: ^5.2.0 version: 5.2.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) + '@vitest/coverage-v8': + specifier: ^3.2.4 + version: 3.2.4(vitest@3.2.4(@types/node@24.12.2)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(msw@2.13.4(@types/node@24.12.2)(typescript@5.6.3))) eslint: specifier: ^9.0.0 version: 9.39.4(jiti@2.6.1) @@ -105,6 +108,10 @@ packages: '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -195,6 +202,10 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -481,6 +492,14 @@ packages: '@types/node': optional: true + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -523,6 +542,10 @@ packages: react: '>=18' react-dom: '>=18' + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@playwright/test@1.59.1': resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==} engines: {node: '>=18'} @@ -909,6 +932,15 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + peerDependencies: + '@vitest/browser': 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -959,6 +991,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -967,6 +1003,10 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -981,6 +1021,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-v8-to-istanbul@0.3.12: + resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -999,6 +1042,9 @@ packages: brace-expansion@1.1.14: resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@5.0.5: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} @@ -1127,12 +1173,18 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + electron-to-chromium@1.5.340: resolution: {integrity: sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enhanced-resolve@5.20.1: resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} @@ -1279,6 +1331,10 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -1316,6 +1372,11 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {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 + hasBin: true + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1358,6 +1419,9 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -1411,10 +1475,32 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1562,6 +1648,13 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1585,6 +1678,14 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1631,6 +1732,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1646,6 +1750,10 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} @@ -1802,10 +1910,18 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -1838,6 +1954,10 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} + test-exclude@7.0.2: + resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} + engines: {node: '>=18'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2039,6 +2159,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + ws@8.20.0: resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} engines: {node: '>=10.0.0'} @@ -2081,6 +2205,11 @@ snapshots: '@adobe/css-tools@4.4.4': {} + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -2203,6 +2332,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@1.0.2': {} + '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -2394,6 +2525,17 @@ snapshots: optionalDependencies: '@types/node': 24.12.2 + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.6': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2440,6 +2582,9 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) + '@pkgjs/parseargs@0.11.0': + optional: true + '@playwright/test@1.59.1': dependencies: playwright: 1.59.1 @@ -2785,6 +2930,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.12.2)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(msw@2.13.4(@types/node@24.12.2)(typescript@5.6.3)))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.12 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.2 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/node@24.12.2)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(msw@2.13.4(@types/node@24.12.2)(typescript@5.6.3)) + transitivePeerDependencies: + - supports-color + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.3 @@ -2845,12 +3009,16 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} + argparse@2.0.1: {} aria-query@5.3.0: @@ -2861,6 +3029,12 @@ snapshots: assertion-error@2.0.1: {} + ast-v8-to-istanbul@0.3.12: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + asynckit@0.4.0: {} balanced-match@1.0.2: {} @@ -2874,6 +3048,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.1.0: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -2984,10 +3162,14 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.340: {} emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + enhanced-resolve@5.20.1: dependencies: graceful-fs: 4.2.11 @@ -3165,6 +3347,11 @@ snapshots: flatted@3.4.2: {} + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -3207,6 +3394,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globals@14.0.0: {} globals@15.15.0: {} @@ -3238,6 +3434,8 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + html-escaper@2.0.2: {} + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -3283,8 +3481,37 @@ snapshots: isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jiti@2.6.1: {} + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -3413,6 +3640,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + math-intrinsics@1.1.0: {} mime-db@1.52.0: {} @@ -3431,6 +3668,12 @@ snapshots: dependencies: brace-expansion: 1.1.14 + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.0 + + minipass@7.1.3: {} + ms@2.1.3: {} msw@2.13.4(@types/node@24.12.2)(typescript@5.6.3): @@ -3487,6 +3730,8 @@ snapshots: dependencies: p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -3499,6 +3744,11 @@ snapshots: path-key@3.1.1: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + path-to-regexp@6.3.0: {} pathe@2.0.3: {} @@ -3640,10 +3890,20 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -3668,6 +3928,12 @@ snapshots: tapable@2.3.2: {} + test-exclude@7.0.2: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 10.5.0 + minimatch: 10.2.5 + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -3857,6 +4123,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + ws@8.20.0: {} xml-name-validator@5.0.0: {} diff --git a/ui/vitest.config.ts b/ui/vitest.config.ts index ebdde6f..1812f9a 100644 --- a/ui/vitest.config.ts +++ b/ui/vitest.config.ts @@ -19,5 +19,20 @@ export default defineConfig({ // e2e/ holds the Playwright suite — it has its own runner and must // not be collected by vitest (different @playwright/test globals). exclude: ["**/node_modules/**", "**/dist/**", "e2e/**"], + coverage: { + // v8 keeps the dep footprint small (built into Node). lcov is + // what SonarCloud's javascript.lcov.reportPaths consumes; text + // and html keep local CI debuggable. + provider: "v8", + reporter: ["text", "lcov", "html"], + reportsDirectory: "./coverage", + include: ["src/**/*.{ts,tsx}"], + exclude: [ + "src/**/*.test.{ts,tsx}", + "src/test-setup.ts", + "src/main.tsx", + "src/vite-env.d.ts", + ], + }, }, } as never); From bcb30ea05fefe9df6fa65bf39f89809d17d6246b Mon Sep 17 00:00:00 2001 From: aksops Date: Fri, 1 May 2026 08:10:12 +0000 Subject: [PATCH 2/2] ci(sonar): pin third-party actions, --ignore-scripts, bump scan v6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the three findings the SonarCloud quality gate raised on PR #7: - pnpm/action-setup pinned to commit SHA (v4 → b906aff). Tag-based refs for third-party actions can be silently rewritten — Sonar S7637. - SonarSource/sonarqube-scan-action bumped v5 → v6 and pinned to SHA fd88b7d. v5 emits a deprecation/security warning on every run. - pnpm install now passes --ignore-scripts (Sonar S6505). Modern vitest + React stack doesn't require lifecycle scripts; verified locally with a clean install + `vitest run --coverage`. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/sonar.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 7de1f5c..4e13c39 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -45,7 +45,10 @@ jobs: go-version-file: go.mod - name: Set up pnpm - uses: pnpm/action-setup@v4 + # Pinned to commit SHA (v4) per supply-chain hygiene — third-party + # actions can be rewritten under a moving tag. Bump by re-running + # `gh api repos/pnpm/action-setup/git/refs/tags/v4`. + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 with: version: 10.33.0 @@ -70,7 +73,12 @@ jobs: ./... - name: UI install - run: pnpm -C ui install --frozen-lockfile + # --ignore-scripts: refuse to execute lifecycle scripts from + # transitive deps (Sonar S6505). vitest + the React stack don't + # require any postinstall; esbuild ships per-platform binaries + # via optional deps. If a future dep needs a build step, add it + # to `pnpm.onlyBuiltDependencies` in ui/package.json instead. + run: pnpm -C ui install --frozen-lockfile --ignore-scripts - name: UI test with coverage run: pnpm -C ui exec vitest run --coverage @@ -89,6 +97,9 @@ jobs: - name: SonarCloud Scan if: steps.token.outputs.present == 'true' - uses: SonarSource/sonarqube-scan-action@v5 + # v6 — v5 emits a deprecation/security warning on every run. + # Pinned to commit SHA per supply-chain hygiene. Bump by + # re-running `gh api repos/SonarSource/sonarqube-scan-action/git/refs/tags/v6`. + uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}