Uniform CI β the shared hard gate
How every bewith-dev TS/JS repo runs the same CI quality gate (lint + test + typecheck) from one shared reusable workflow, instead of each repo copy-pasting its own. This is the CI half of the platform's hard-gate goal; the coding rules it runs live in coding-standards.md and @bewith-dev/eslint-plugin.
Statusβ
- The reusable workflow lives here β
.github/workflows/reusable-ci.ymlin bewith-docs, taggedci-v1so callers pin a stable ref. (Relocated frombewith-dev/.githubβ a reusable workflow can live in any repo; see Target design.) - The per-repo caller is templated β
templates/consumer-repo/.github/workflows/ci.yml. - Open work is the rollout β adopt the caller per repo and turn on the required branch-protection check (the adoption sweep below).
Current state (org scan, 2026-06-01)β
Before this workflow there was no shared reusable CI gate β every repo rolled its own pr-validation / pr-tests, and coverage was uneven:
| Repo | lint | test | typecheck | notes |
|---|---|---|---|---|
| backend-services | β | β | β | pr-tests.yml (lint/test/build, sharded) |
| management-webapp | β | β | β | split across pr-validation + tests.yml (Jest + Playwright) |
| organization-dashboard | β | β | β | pr-validation.yml |
| support-tool-frontend | β | β | β | pr-validation.yml |
| sqs-consumer | β | β | β | lint + build only |
| design-system | β | β | β | lint + build |
| with-calendar | β | β | β | lint + build |
| facility-rentals | β | β | β | deploy-only (Docker build on tag) |
| cognito-lambdas | β | β | β | build/deploy only |
| bewith-consumer-frontend | β | β | β | no .github/workflows at all |
| migrations-tool | β | β | β | ops workflows only β PRs ungated |
| loki-cloudwatch-forwarder | β | β | β | β |
| Kubernetes-deployments | β | β | β | ops/GitOps only |
Findings:
- No cross-repo reuse. The only
workflow_callreusables (backend-servicesbuild-*/deploy-*) are self-consumed deploy orchestration, not a shared CI gate.aws-ecs-task-definition-creatoris a composite action, not a reusable workflow. tsc --noEmittypecheck runs nowhere β type errors are only caught implicitly bynext build/ the build step.- Four repos have no PR gate at all (including the modern
bewith-consumer-frontend). - Version drift in
@bewith-dev/eslint-plugin(1.9.1β2.1.1), andbewith-consumer-frontenddoesn't depend on it.
Target designβ
One reusable workflow, hosted org-wide, consumed by a tiny caller in each repo, required by branch protection.
-
Host it in
bewith-dev/bewith-docsat.github/workflows/reusable-ci.yml,on: workflow_callβ one shared home for the platform. A GitHub reusable workflow can beuses:-referenced from any repo; the org.githubrepo is not required (it is special only for the org profile, default community-health files, and starter templates β a reusable workflow is none of those). Consumers pin a dedicatedci-vNtag, so bewith-docs content releases that don't move that tag never affect them. One-time human step: set bewith-docs β Settings β Actions β General β Access = "Accessible from repositories in the organization", and confirm the org "Allow actions and reusable workflows" policy permits it. -
The reusable workflow β parameterized, fail-fast, package-manager-aware. Inputs:
node-version(default20.x),package-manager(npm|pnpm|yarn, defaultnpm),run-tests(defaulttrue). Onecijob, sequential steps:# bewith-dev/bewith-docs/.github/workflows/reusable-ci.yml β abridged; full file in this repoon:workflow_call:inputs:node-version: { type: string, default: '20.x' }package-manager: { type: string, default: npm } # npm | pnpm | yarnrun-tests: { type: boolean, default: true }jobs:ci:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- if: ${{ inputs.package-manager == 'pnpm' }}uses: pnpm/action-setup@v4 # pnpm must exist before setup-node's cache- uses: actions/setup-node@v4with:node-version: '${{ inputs.node-version }}'cache: '${{ inputs.package-manager }}'registry-url: 'https://npm.pkg.github.com'- run: <pm> install # npm ci / pnpm install --frozen-lockfile / yarn --frozen-lockfileenv: { NODE_AUTH_TOKEN: '${{ secrets.DEPLOY_TOKEN || secrets.GITHUB_TOKEN }}' }- run: <pm> run lint # eslint via @bewith-dev/eslint-plugin- if: ${{ hashFiles('tsconfig.json') != '' }}run: npx --no-install tsc --noEmit # the typecheck nobody runs today; skipped for pure-JS repos- if: ${{ inputs.run-tests }}run: <pm> run testEach repo must expose
lintandtestpackage scripts and atsc --noEmit-abletsconfig.json. Thelintscript should fail on errors (eslint .or alint:errors-style script aliased tolint). -
The per-repo caller (replaces each repo's hand-rolled pr-validation) β templated at
templates/consumer-repo/.github/workflows/ci.yml:# <repo>/.github/workflows/ci.ymlname: CIon: { pull_request: { branches: [master, main] } }jobs:ci:uses: bewith-dev/bewith-docs/.github/workflows/reusable-ci.yml@ci-v1secrets: inheritwith: { node-version: '20.x', package-manager: pnpm } -
Org branch protection requires the
CI / cicheck β a repo cannot merge a red PR. This is the actual hard gate; the reusable workflow is just how the check gets there uniformly. -
Auto-bump the shared eslint-plugin β add Renovate (or Dependabot) org-wide to open a PR in every repo when
@bewith-dev/eslint-pluginpublishes a new version. Closes the version-drift gap and the "PR after a bump" ask without manual chasing. Pin the reusable workflow by@v1tag and move the tag on release.
Adoption sweep (existing repos)β
The template covers new repos; existing repos get retrofitted. Per repo:
- Ensure
@bewith-dev/eslint-pluginis a devDependency at the current version and the eslint config extends it. - Ensure
package.jsonhaslint:errors,test, and atsc --noEmit-abletsconfig. - Add the caller
ci.yml(above); delete the bespokepr-validation/pr-tests. - Turn on branch protection requiring
CI / ci.
Sweep checklist (from the scan):
- Add CI from scratch:
bewith-consumer-frontend,migrations-tool,loki-cloudwatch-forwarder,facility-rentals,cognito-lambdas. - Add the missing
teststep:sqs-consumer,design-system,with-calendar. - Add
tsc --noEmit: all repos (runs nowhere today). - Onboard
bewith-consumer-frontendto@bewith-dev/eslint-plugin(it doesn't depend on it). - Resolve version drift via Renovate (
1.9.1β latest across all). - PHP repos (
superco-consumer,superco-management) are out of scope for this (no eslint) β they need a separate PHPCS/PHPStan reusable workflow as a parallel track.
Scope noteβ
This runbook is TS/JS. PHP needs its own reusable workflow (PHPCS/PHPStan), owned by legacy-php-guide β but the Yii2 repos are deprecation targets, so invest minimally there.
See alsoβ
coding-standards.mdΒ§Enforcing across repos β the strategy this runbook implements.enforcement-tiers.mdβ where this CI gate sits among the platform's enforcement layers.- The uniform-CI ClickUp task tracks the build-out.