From 981a634dfbe4859c0db33de89b173c3bcc9cb132 Mon Sep 17 00:00:00 2001 From: jubnl Date: Tue, 25 Aug 2026 19:42:45 +0200 Subject: [PATCH] chore(sonar): switch to cloud --- .github/workflows/sonarqube.yml | 238 +++++++++++++++++++++++++------- sonar-project.properties | 3 +- 2 files changed, 187 insertions(+), 54 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 6caba32ab..1e8bf0c6c 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -3,28 +3,51 @@ name: SonarQube permissions: contents: read -# Push to dev only, deliberately. +# SonarQube Cloud analysis: pushes to dev and main produce branch analysis, +# pull requests get PR analysis with decoration on the PR itself (the scanner +# picks the context up from the Actions environment). Fork PRs are skipped — +# they don't receive SONAR_TOKEN, so the guard on the coverage jobs stops the +# whole run (the scan job follows through `needs:`) instead of failing it. +# Automatic Analysis must stay disabled on sonarcloud.io — it is mutually +# exclusive with CI-based analysis, and it cannot import the lcov coverage +# this workflow produces. # -# The SonarQube instance is Community Edition, which has no branch or -# pull-request analysis: the project holds exactly one view. Analysing a feature -# branch would overwrite that view with the branch's numbers until somebody -# scanned dev again, so the one branch that may publish is the one everybody -# reads. workflow_dispatch stays available for a manual re-run. +# Coverage is generated by four parallel jobs (same split as test.yml) instead +# of one serial `npm run test:cov` — the serial run takes ~30 minutes, the +# fan-out about half that. The scan job then drops each package's lcov into +# the path sonar.javascript.lcov.reportPaths expects and runs the scanner. on: push: - branches: [dev] + branches: [main, dev] + pull_request: + branches: [main, dev] + # Same code filter as test.yml (plus the sonar config itself): a PR that + # touches none of these can't change the analysis, so don't spend the + # ~15-minute coverage fan-out on it. Pushes stay unfiltered so the branch + # view is always refreshed from the merge result. + paths: + - 'server/**' + - 'client/**' + - 'shared/**' + - 'plugin-sdk/**' + - 'package.json' + - 'package-lock.json' + - 'sonar-project.properties' + - '.github/workflows/sonarqube.yml' workflow_dispatch: jobs: - scan: - name: Coverage & Scan + server-coverage: + name: Server Coverage runs-on: ubuntu-latest + # Fork PRs don't get SONAR_TOKEN, so the scan at the end could never run — + # skip the coverage work too rather than produce artifacts nothing reads. + # (Same guard on all four coverage jobs; the scan job inherits the skip + # through `needs:`.) + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + steps: - uses: actions/checkout@v6 - with: - # Sonar reads git blame to date each issue and to decide what counts as - # new code. A shallow clone leaves it blind to both. - fetch-depth: 0 - uses: actions/setup-node@v6 with: @@ -35,59 +58,168 @@ jobs: - name: Install dependencies run: npm ci - - name: Install plugin-sdk dependencies - # plugin-sdk is not a root workspace (workspaces = client, server, shared) - # — it ships as its own npm package with its own lockfile, so the root - # npm ci does not reach it. Its coverage is part of test:cov all the same. - run: cd plugin-sdk && npm ci - - name: Ensure @swc/core's Linux binary for unplugin-swc - # Same reason as test.yml: the lockfile was generated on Windows and omits - # @swc/core's Linux optional native binary, so npm ci skips it on the - # runner and the server's SWC transform cannot load. - # - # @testing-library/dom rides along, and has to. This install re-resolves - # the tree, and --legacy-peer-deps means npm does not put peer - # dependencies back — @testing-library/dom is a peer of - # @testing-library/react and not a direct dependency of the client, so it - # was silently dropped and all 616 client test files died on - # "Cannot find module". In test.yml this step lives in the server job, - # which never runs client tests, so the collision only appears once the - # jobs are folded into one. The version is read from the peer range rather - # than pinned here, so a bump to @testing-library/react carries over. + # The lockfile was generated on Windows and omits @swc/core's Linux + # optional native binary, so npm ci/install skips it on the runner. + # Install the matching version explicitly so the server's SWC transform + # (server/vitest.config.ts) can load. run: | SWC_VERSION=$(node -p "require('@swc/core/package.json').version") - DOM_RANGE=$(node -p "require('@testing-library/react/package.json').peerDependencies['@testing-library/dom']") - npm install --no-save --legacy-peer-deps \ - "@swc/core-linux-x64-gnu@$SWC_VERSION" \ - "@testing-library/dom@$DOM_RANGE" - - - name: Verify the test toolchain survived that install - # Cheap tripwire: if a future dependency bump loses another peer the same - # way, this fails here with the name of the module instead of 600 - # identical test failures twenty minutes later. - run: node -e "require.resolve('@testing-library/dom'); require.resolve('@swc/core')" + npm install --no-save --legacy-peer-deps "@swc/core-linux-x64-gnu@$SWC_VERSION" - name: Build shared - # Only shared, not the root `build`. server and client run their suites - # from source through vitest, so building them would add a tsc pass and a - # full vite bundle that nothing here reads. run: npm run build --workspace=shared - name: Run tests with coverage - # One script for all four packages — it already ends with - # `--prefix plugin-sdk`. Each writes lcov to /coverage/, which is - # where sonar.javascript.lcov.reportPaths looks. Without those files every - # line reports as uncovered and the dashboard shows a collapse that did - # not happen, so this step failing has to stop the scan rather than let it - # publish a false number. - run: npm run test:cov + run: cd server && npm run test:coverage + + - name: Upload coverage + uses: actions/upload-artifact@v6 + with: + name: sonar-server-coverage + path: server/coverage/ + retention-days: 1 + + client-coverage: + name: Client Coverage + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + cache-dependency-path: package-lock.json + + # Only the shared and client workspaces — a full root npm ci is not + # needed, and the scoped install keeps @testing-library/dom (a peer of + # @testing-library/react) in the tree without the re-resolution dance + # the folded coverage job used to need. + - name: Install dependencies + run: npm ci --workspace shared && npm ci --workspace client + + - name: Build shared + run: npm run build --workspace=shared + + - name: Run tests with coverage + run: cd client && npm run test:coverage + + - name: Upload coverage + uses: actions/upload-artifact@v6 + with: + name: sonar-client-coverage + path: client/coverage/ + retention-days: 1 + + shared-coverage: + name: Shared Coverage + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci --workspace shared + + - name: Run tests with coverage + run: cd shared && npm run test:coverage + + - name: Upload coverage + uses: actions/upload-artifact@v6 + with: + name: sonar-shared-coverage + path: shared/coverage/ + retention-days: 1 + + plugin-sdk-coverage: + name: Plugin SDK Coverage + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + defaults: + run: + working-directory: plugin-sdk + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + # 22 matches the TREK server runtime and has node:sqlite, which the + # dev-server tests exercise (same choice as test.yml and + # publish-plugin-sdk.yml). + node-version: 22 + + # plugin-sdk is standalone (own lockfile, not a root workspace), so it + # installs from its own directory. + - name: Install dependencies + run: npm ci + + - name: Run tests with coverage + run: npm run test:coverage + + - name: Upload coverage + uses: actions/upload-artifact@v6 + with: + name: sonar-plugin-sdk-coverage + path: plugin-sdk/coverage/ + retention-days: 1 + + scan: + name: Scan + runs-on: ubuntu-latest + # Coverage jobs failing must stop the scan: a missing lcov file would make + # every line of that package report as uncovered, and the dashboard would + # show a collapse that did not happen. + needs: [server-coverage, client-coverage, shared-coverage, plugin-sdk-coverage] + + steps: + - uses: actions/checkout@v6 + with: + # Sonar reads git blame to date each issue and to decide what counts + # as new code. A shallow clone leaves it blind to both. + fetch-depth: 0 + + - name: Download server coverage + uses: actions/download-artifact@v6 + with: + name: sonar-server-coverage + path: server/coverage/ + + - name: Download client coverage + uses: actions/download-artifact@v6 + with: + name: sonar-client-coverage + path: client/coverage/ + + - name: Download shared coverage + uses: actions/download-artifact@v6 + with: + name: sonar-shared-coverage + path: shared/coverage/ + + - name: Download plugin-sdk coverage + uses: actions/download-artifact@v6 + with: + name: sonar-plugin-sdk-coverage + path: plugin-sdk/coverage/ - name: Scan uses: SonarSource/sonarqube-scan-action@v8 env: + # SonarQube Cloud is the scan action's default host, so no + # SONAR_HOST_URL — the token is a sonarcloud.io token for the + # liketrek org. SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} with: # New Code is configured as "previous version", so this string is what # draws that line — changing it re-bases every "new code" metric. diff --git a/sonar-project.properties b/sonar-project.properties index 9ac715f1f..7a3d1a42a 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,4 +1,5 @@ -sonar.projectKey=TREK +sonar.projectKey=liketrek_TREK +sonar.organization=liketrek sonar.projectName=TREK sonar.sourceEncoding=UTF-8