mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
CI: add a jest-config typecheck gate and a non-blocking lint report (#10119)
Nothing in CI ran `tsc --noEmit` or ESLint. That is why a duplicated `transformIgnorePatterns` key sat in `packages/core/jest.config.ts` undetected (TS1117, fixed in #10116), and why ESLint had been broken repo-wide long enough for 93 of 93 projects to fail (fixed in #10117). Both gaps are now covered, deliberately with very different postures. `typecheck-configs` is a real gate. `tools/tsconfig.jest-configs.json` type-checks all 95 jest.config.ts files and is green today, so the job fails if that stops being true. It is built to need nothing but the compiler — no `extends`, `types: []`, `noResolve`, and a small shim in `tools/jest-config-globals.d.ts` for the 53 CommonJS configs plus the one `@nx/jest` import in the root config. The compile is 0.5s across all 95 files; the whole job measured 68s in CI, dominated by runner provisioning. A cold `yarn install` on this fleet is measured in hours, so avoiding one is the entire design constraint. Proven, not assumed: exit 0 on the current tree, and exit 2 with TS1117 when a duplicate key is reintroduced. `lint` is NOT a gate. It runs `nx run-many -t lint` and writes the outcome to the run summary. The repair in #10117 left roughly 2,650 errors and 6,100 warnings, all predating the job, so a blocking lint gate would wedge every PR on day one. Non-blocking at both the step level (so the summary still runs) and the job level (so a cache miss or install failure on the fleet cannot redden the check either). Verified end to end on this PR: ESLint exited 1 with real findings, the summary step ran, and the check reported green. TypeScript is installed pinned with `--ignore-scripts` rather than fetched through `npx --yes`, which SonarCloud correctly flagged as a vulnerability twice (S6505/S8543) for running a network-fetched package's lifecycle scripts in CI. It is also faster. Neither job is wired into branch protection; `develop` has no required status checks at all, so these are visible signals rather than hard gates. The tracking task for burning down the ESLint backlog lives in the agent workspace at `knowledge/TASKS.md`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
52ae8427fe
commit
be59bce929
@@ -4,6 +4,7 @@
|
||||
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
|
||||
"ignoreRegExpList": ["GHSA-[A-Za-z0-9-]+"],
|
||||
"words": [
|
||||
"typecheck",
|
||||
"ELIFECYCLE",
|
||||
"PRIMARYKEY",
|
||||
"NOTNULL",
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
name: 'Static Checks'
|
||||
|
||||
# Two checks that nothing in CI did before, added after a duplicated `transformIgnorePatterns`
|
||||
# key sat in `packages/core/jest.config.ts` undetected (TS1117):
|
||||
#
|
||||
# * `typecheck-configs` — `tsc --noEmit` over every jest.config.ts, sub-second for the compile
|
||||
# itself and under a minute for the whole job. This one is REAL: it is green today and it
|
||||
# fails the job if it ever stops being. It is the check that would have caught that bug.
|
||||
# * `lint` — reports the ESLint backlog. Deliberately NON-BLOCKING (`continue-on-error`), and
|
||||
# it must stay that way until the backlog below is worked through. As of the repair in
|
||||
# #10117 the workspace reports roughly 2,650 errors and 6,100 warnings, so a blocking lint
|
||||
# gate would wedge every PR on day one. The tracking task lives in the agent workspace at
|
||||
# `knowledge/TASKS.md` ("Burn down the ESLint backlog").
|
||||
#
|
||||
# Promote `lint` to blocking only once `nx run-many -t lint` is actually green.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# Least-privilege scope for the automatic GITHUB_TOKEN: these jobs only read the checkout.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NODE_MODULES_ARCHIVE: node-modules.tar.zst
|
||||
|
||||
jobs:
|
||||
typecheck-configs:
|
||||
name: typecheck-configs
|
||||
runs-on: ${{ vars.RUNNER_LINUX_X64_4 || 'ubuntu-latest' }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
# Read-only job; the checkout credential does not need to outlive the step.
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
# No `yarn install`. `tools/tsconfig.jest-configs.json` is standalone — `types: []`,
|
||||
# `noResolve`, and a two-declaration shim in `tools/jest-config-globals.d.ts` — precisely so
|
||||
# this job needs nothing but the compiler. The compile itself is well under a second across
|
||||
# all 95 configs; fetching the compiler dominates. Against the hours a cold `yarn install`
|
||||
# costs on this fleet, that is the whole trade.
|
||||
#
|
||||
# Installed rather than run through `npx --yes`, which fetches on demand and runs the
|
||||
# fetched package's lifecycle scripts — Sonar flags that as a vulnerability (rules S6505
|
||||
# and S8543). Exact version, no scripts, no lockfile writes, and into RUNNER_TEMP so it
|
||||
# cannot disturb the checkout. It is also faster: ~4s to install against ~37s for npx.
|
||||
- name: Install TypeScript
|
||||
run: npm install --no-save --no-package-lock --ignore-scripts --prefix "$RUNNER_TEMP/tsc" typescript@5.9.3
|
||||
|
||||
- name: Type-check every jest.config.ts
|
||||
run: |
|
||||
"$RUNNER_TEMP/tsc/node_modules/.bin/tsc" -p tools/tsconfig.jest-configs.json
|
||||
|
||||
lint:
|
||||
name: lint (non-blocking)
|
||||
runs-on: ${{ vars.RUNNER_LINUX_X64_8 || 'ubuntu-latest' }}
|
||||
# Non-blocking at the JOB level, not just on the ESLint step. The step-level flag below keeps
|
||||
# a red lint result from failing the job; this keeps anything else in the job — a cache miss,
|
||||
# a registry hiccup, an install failure on the fleet — from doing it either. The requirement
|
||||
# is that this job cannot redden CI for anyone, and a job that goes red because a runner had
|
||||
# a bad day would break that just as surely as a lint error would.
|
||||
continue-on-error: true
|
||||
# A cold dependency install on this fleet is measured in hours, not minutes. The ceiling costs
|
||||
# nothing on the warm path, which is a cache lookup.
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
# Action versions deliberately match `build.yml` rather than the newer majors used elsewhere:
|
||||
# this job consumes the node_modules cache that workflow writes, so producer and consumer are
|
||||
# kept on the same actions.
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Restore node_modules archive
|
||||
# Best effort: the same key the build workflow writes, so a push that already built this
|
||||
# exact lockfile reuses that tree instead of installing again. The restore script below
|
||||
# installs from scratch when the archive is absent, so a miss is not fatal.
|
||||
uses: actions/cache/restore@v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: ${{ env.NODE_MODULES_ARCHIVE }}
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-node-modules-${{ hashFiles('yarn.lock', 'package.json', 'patches/**', '.scripts/postinstall.js') }}
|
||||
|
||||
# Placed AFTER the cache restore because this action rewrites yarn.lock's resolved URLs, and
|
||||
# `hashFiles()` in the key above evaluates at step runtime — configuring the registry first
|
||||
# would move the key and split the cache namespace by VIP reachability.
|
||||
- name: Configure Registry
|
||||
uses: ever-co/ever-gauzy/.github/actions/configure-registry@aa4ee19926fabcf820aa1294385a76aec6bdb548
|
||||
with:
|
||||
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
||||
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
||||
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
||||
expect-vip: ${{ vars.RUNNER_LINUX_X64_8 != '' }}
|
||||
|
||||
- name: Restore node_modules
|
||||
shell: bash
|
||||
run: .github/scripts/restore-node-modules.sh
|
||||
|
||||
# NON-BLOCKING, on purpose. See the header. `--nxBail=false` so one failing project still
|
||||
# lets every other project report; without it the first failure hides the rest.
|
||||
- name: Run ESLint (report only)
|
||||
id: eslint
|
||||
continue-on-error: true
|
||||
run: yarn nx run-many -t lint --nxBail=false --parallel=2
|
||||
|
||||
# Surface the size of the backlog on the run page, so it is visible without opening logs and
|
||||
# so progress against it is legible over time.
|
||||
- name: Summarize
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "## ESLint"
|
||||
echo
|
||||
if [ "${{ steps.eslint.outcome }}" = "success" ]; then
|
||||
echo "\`nx run-many -t lint\` is **green**."
|
||||
echo
|
||||
echo "This job can now be promoted to blocking: drop \`continue-on-error\` from the"
|
||||
echo "\`Run ESLint\` step in \`.github/workflows/static-checks.yml\`."
|
||||
else
|
||||
echo "\`nx run-many -t lint\` reported problems. **This does not fail CI** — the"
|
||||
echo "backlog predates the job (see \`knowledge/TASKS.md\` in the agent workspace)."
|
||||
echo
|
||||
echo "Full output is in the \`Run ESLint (report only)\` step above."
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Minimal ambient declarations for `tools/tsconfig.jest-configs.json`.
|
||||
*
|
||||
* That project deliberately sets `"types": []` so it can run with nothing installed but
|
||||
* TypeScript itself — a CI job that needed `yarn install` first would cost minutes instead of
|
||||
* seconds, and the whole point of the check is that it is cheap enough to run on every PR.
|
||||
*
|
||||
* `@types/node` is therefore unavailable, but 53 of the workspace's jest configs are CommonJS
|
||||
* (`module.exports = { ... }`). These two declarations are all they need. Do not grow this file
|
||||
* into a general-purpose Node shim — if a config needs more than this, it probably belongs in a
|
||||
* project that has real types.
|
||||
*/
|
||||
declare const module: { exports: unknown };
|
||||
declare const require: (id: string) => unknown;
|
||||
|
||||
/**
|
||||
* The root `jest.config.ts` is the only one of the 95 that imports anything; the other 94 are
|
||||
* self-contained data literals. Declaring the module here keeps the check at 95/95 with no
|
||||
* exclusion to remember, without resolving into `node_modules`.
|
||||
*
|
||||
* This deliberately does not describe `@nx/jest`'s real API — the point of the check is the
|
||||
* syntax and shape of the config files themselves, not the types of a third-party package.
|
||||
*/
|
||||
declare module '@nx/jest' {
|
||||
export function getJestProjectsAsync(): Promise<unknown>;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
// Type-checks every `jest.config.ts` in the workspace, and nothing else.
|
||||
//
|
||||
// Why this exists: a duplicated `transformIgnorePatterns` key sat in
|
||||
// `packages/core/jest.config.ts` undetected (TS1117). Node 24's native type-stripping hid it,
|
||||
// but on any runtime without stripping Jest fell into its ts-node path and failed to load the
|
||||
// config at all — zero suites, zero tests. Nothing in CI ran `tsc --noEmit`, so nothing caught
|
||||
// it. See `.github/workflows/static-checks.yml`.
|
||||
//
|
||||
// Deliberately does NOT extend the root tsconfig: this must run with nothing installed but
|
||||
// TypeScript itself, so the CI job costs seconds rather than a full `yarn install`. That is
|
||||
// also why `types` is empty and `tools/jest-config-globals.d.ts` supplies `module`/`require`
|
||||
// for the CommonJS configs.
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
// The configs are plain data. Resolving `preset: '../../jest.preset.js'` and friends is
|
||||
// not the point of this check, and doing so would drag in the dependency graph this
|
||||
// project exists to avoid.
|
||||
"noResolve": true,
|
||||
"types": []
|
||||
},
|
||||
"include": [
|
||||
"jest-config-globals.d.ts",
|
||||
"../jest.config.ts",
|
||||
"../apps/*/jest.config.ts",
|
||||
"../packages/*/jest.config.ts",
|
||||
"../packages/plugins/*/jest.config.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user