From 7547c80502c4fea86258255ee9eaf6843188c7df Mon Sep 17 00:00:00 2001 From: Ovidiu Galatan Date: Mon, 27 Jul 2026 17:00:59 +0300 Subject: [PATCH] ci: verify formatting, types, build, and tests on every PR WHAT: Add .github/workflows/ci.yml, running on pull_request and on push to trunk: npm ci, npm run check, npm run build, both jest suites, and a guard that package.json and MCP_WORDPRESS_REMOTE_VERSION agree. Concurrency is grouped per PR with cancel-in-progress, permissions are contents: read, and the trigger is pull_request rather than pull_request_target so fork PRs get a read-only token and no secrets. WHY: release.yml was the only workflow and it fires on release, so nothing ever ran on a PR. Every problem found this week traces to that gap: Prettier had drifted across 35 tracked files, `npm run check` had never once executed, a bare tsc in that script was silently overwriting the tsup bundle, and two dependabot PRs were merged reporting "no checks". A local pre-commit hook cannot close this - dependabot commits are created server-side and never run local git hooks. The version guard is included because AGENTS.md states the rule and a check can enforce it; the drift has shipped a token directory named wordpress-remote-undefined once already. --- .github/workflows/ci.yml | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3adcf28 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: CI + +on: + pull_request: + push: + branches: [trunk] + +# A new push to a PR makes the in-flight run obsolete; don't burn minutes on it. +concurrency: + group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: verify + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + + - name: Install dependencies + run: npm ci + + # Formatting plus a whole-project typecheck. tsc must keep --noEmit here: + # tsconfig sets outDir to ./dist, so a bare tsc would overwrite the tsup + # bundle with per-file output. + - name: Check formatting and types + run: npm run check + + - name: Build + run: npm run build + + # Both suites. tests/unit/ alone silently skips the integration tests. + - name: Run tests + run: npx jest tests/unit/ tests/integration/ --no-coverage + + # package.json and the constant are independent sources of truth, and + # drifting apart once shipped a token directory named + # wordpress-remote-undefined. + - name: Verify version constant matches package.json + run: | + PKG_VERSION="$(node -p "require('./package.json').version")" + SRC_VERSION="$(node -e " + const s = require('fs').readFileSync('src/lib/config.ts', 'utf8'); + const m = s.match(/MCP_WORDPRESS_REMOTE_VERSION = '([^']+)'/); + process.stdout.write(m ? m[1] : ''); + ")" + if [ "${PKG_VERSION}" != "${SRC_VERSION}" ]; then + echo "Version mismatch: package.json is '${PKG_VERSION}' but MCP_WORDPRESS_REMOTE_VERSION in src/lib/config.ts is '${SRC_VERSION}'." + exit 1 + fi + echo "Version constant matches package.json: ${PKG_VERSION}"