chore(ci): scan this repo with SonarQube (phase 4, slice 0)
All checks were successful
PR Checks / client-build (pull_request) Successful in 14s
PR Checks / frozen-manifest (pull_request) Successful in 34s
PR Checks / server-tests (pull_request) Successful in 8m50s

Until now this was the one part of the platform that had never been scanned.
The 75 files here arrived in the Phase 3 extraction and left their Sonar
history behind in core's project, so a whole module's worth of shipped code
has no dashboard at all.

Adds sonar-project.properties (project key Module-uo) and a sonarqube.yml
mirroring website's: push to main, never a PR gate, nothing waiting on the
quality gate.

Two things differ from core's config, both because this repo is shaped
differently:

  - There is no src/ to point sonar.sources at — the server half keeps
    boot.js/core.js/index.js at server/ root beside its subdirectories — so
    the whole tree is included and the non-source parts are excluded. That
    direction is deliberate: a new top-level server directory is scanned by
    default rather than silently unscanned.
  - server/scripts and client/scripts are IN. checkImports.js and
    checkExternals.js are the enforcement of MODULE_API.md 5.1 and 3.6, they
    carry their own test suites, and both have already shipped defects a
    reviewer missed. Build code that decides whether a release is allowed out
    is not throwaway code.

The workflow builds the client chunk before running either suite, for the
reason pr-checks.yml already calls load-bearing: build.test.js and
registration.test.js read dist/entry.js and SKIP without it, so the other
order reports coverage for a suite that quietly asked less than it looks like
it did.

Both suites run from the repo root rather than with --prefix, so the LCOV SF:
paths come out repo-root-relative and resolve against sonar.sources. That is
why the server suite's --require is spelled out here instead of reusing
`npm test --prefix server`, whose path is relative to server/.

Verified locally: 385 server test cases across 57 covered files and 40 client
cases, both LCOV and Generic Test Execution XML well-formed with
repo-root-relative paths.

Needs one-time setup in the Gitea UI before it can run — secret SONAR_TOKEN
and variable SONAR_HOST_URL, same as the other repos.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-12 02:36:11 -05:00
parent e81b61d044
commit 62c8ee68b4
4 changed files with 234 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
# Run SonarQube static analysis against the code that just landed on `main` and
# report the results to the self-hosted SonarQube server for review. This is
# intentionally NON-BLOCKING: it triggers on push to main (i.e. AFTER merge),
# not on pull_request, so it never gates a PR. It complements pr-checks.yml
# (which gates PRs) and release.yml (which publishes the bundle) — this one only
# feeds the dashboard.
#
# Mirrors RunicGateway/website's sonarqube.yml, for the same reason pr-checks.yml
# does: this module is two npm packages shaped like that repo's `server/` and
# `client/`, and it is loaded into that repo's process. Until now it was the one
# part of the platform that had never been scanned — 75 files that arrived in the
# Phase 3 extraction with core's Sonar history left behind in core's project.
#
# Prerequisites (one-time, in the Gitea UI — Repo → Settings → Actions):
# • Secret SONAR_TOKEN — a SonarQube "Analysis" token generated at
# My Account → Security in SonarQube for the
# Module-uo project (or a global one).
# • Variable SONAR_HOST_URL — the SonarQube base URL on your LAN, e.g.
# http://192.168.0.56:9000
# (kept as a variable, not committed, so the internal address stays out of git.)
#
# The runner (self-hosted `ubuntu-latest`, same as the other workflows) must be
# able to reach SONAR_HOST_URL on your network. Nothing here waits on the
# SonarQube Quality Gate, so a failing gate does not fail this job — check the
# dashboard when you want to.
name: SonarQube
on:
push:
branches: [main]
# Allow re-running the analysis on demand from the Actions tab.
workflow_dispatch: {}
concurrency:
group: sonarqube-${{ github.ref }}
cancel-in-progress: true
jobs:
analysis:
runs-on: ubuntu-latest
steps:
- name: Check out (full history for accurate new-code + blame)
uses: actions/checkout@v4
with:
# SonarQube uses git history to attribute issues to authors and to
# compute "new code". A shallow clone degrades both.
fetch-depth: 0
# Node 22, where pr-checks.yml pins 20: the built-in `lcov` coverage
# reporter this job depends on needs >= 22. The version that matters for
# correctness is the one in pr-checks.yml, which matches the core process
# this module is loaded into; nothing here ships.
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install deps for both halves
run: |
npm ci --prefix server
npm ci --prefix client
# The chunk has to exist before the client suite runs: build.test.js and
# registration.test.js read `client/dist/entry.js`, and both SKIP when
# there is no build. Run the other way round they skip silently and this
# job reports coverage for a suite that quietly asked less than it looks
# like it did — the same ordering pr-checks.yml calls load-bearing.
- name: Build the client chunk
run: npm run build --prefix client
# SonarQube runs static analysis only — it never executes the test suite,
# so we must produce the coverage report ourselves and hand it to the
# scanner (see sonar.javascript.lcov.reportPaths in sonar-project.properties).
#
# Both suites are invoked from the REPO ROOT rather than with `--prefix`,
# so the LCOV `SF:` paths come out repo-root-relative (`server/router/...`,
# `client/src/...`) and resolve against sonar.sources. That is also why the
# server suite's `--require` is spelled out here instead of reusing
# `npm test --prefix server`, whose path is relative to `server/`.
- name: Generate server test coverage (LCOV)
run: |
mkdir -p server/coverage
node --test --experimental-test-coverage \
--require ./server/test/_setup.js \
--test-reporter=spec --test-reporter-destination=stdout \
--test-reporter=lcov --test-reporter-destination=server/coverage/lcov.info \
--test-reporter=./scripts/sonar-test-reporter.mjs --test-reporter-destination=server/coverage/test-execution.xml \
server/test/*.test.js
- name: Generate client test coverage (LCOV)
run: |
mkdir -p client/coverage
node --test --experimental-test-coverage \
--test-reporter=spec --test-reporter-destination=stdout \
--test-reporter=lcov --test-reporter-destination=client/coverage/lcov.info \
--test-reporter=./scripts/sonar-test-reporter.mjs --test-reporter-destination=client/coverage/test-execution.xml \
client/test/*.test.js
- name: Run SonarQube scan
uses: sonarsource/sonarqube-scan-action@v4
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}