mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
6e61e1821c
#3573 migrated the moderator surfaces to the standalone app and four preview specs kept probing paths that now 302 off-origin. `preview / smoke-tests` went red and STAYED red on every PR based after it, and because that job is report-only nothing stopped: three PRs merged through it in the four hours before someone looked, and an agent nearly attributed a genuine `main` breakage to their own PR because it arrived inside an already-red set. #4179 fixed the four failures. This is the recurrence guard. It scans the preview specs for `/moderator/*` literals and fails when one has migrated or resolves to no page, so the next migration reddens the machine of whoever performs it — `test:unit:run` is on the before-committing list — as one named test carrying the path and the fix, rather than as browser assertions in a job whose red is ambient. Be precise about what that buys, because the obvious reading is wrong: it does NOT make the fact blocking. The `unit` job is `continue-on-error: true` and `main` has no required status checks, so this is report-only too. What changes is where and how the failure appears. The three specs that assert the redirect on purpose carry an inline `@migrated-route-probe` marker, checked in both directions — a path that comes BACK to this app strands the assertion the same way. A marked line may hold only one probe, or the marker would excuse the others silently. Two properties the scan needs and did not get for free: the positive control asserts the ENFORCED partition rather than the total, because `it.each([])` registers zero tests and exits 0 (measured), so marking every line would empty the guard with nothing to show for it; and the capture stops before `?`, since `/moderator/reports?status=Pending` is the natural shape of a queue probe and the exact route the incident was about. Documentation this turned up as stale: the convention-guard list named four of seven, `test:lint- rules` is invoked by no workflow (those guards run because they match the `unit` project), the root test-command list omitted the packages and apps suites entirely, and the SvelteKit standard had no testing section at all despite all three apps having one. The `unit` job's flip-to-blocking note now carries what was measured today rather than leaving it to be rediscovered: 8 of 39 recent runs had a red `Unit tests` STEP across 8 distinct branches, which reads as flake but was trunk-red from a ledger test failing on every PR (fixed by #4191); 5 of 5 green after it. Plus the two traps — the run-level `conclusion` says success while the step under it failed, and this workflow is `pull_request`-only, so a trunk-red test shows up as every PR reddening at once. Refs 868kubuz6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
450 lines
23 KiB
YAML
450 lines
23 KiB
YAML
name: Lint
|
|
|
|
# Regression guard for code quality.
|
|
#
|
|
# Typecheck is full-repo, and as of talos-infra #855 it is now a NARROW job: it
|
|
# runs only where the in-cluster Tekton `pr-check` pipeline does not reach. For
|
|
# an internal PR targeting `main` — every PR merged here in living memory — the
|
|
# authoritative typecheck is the `tekton / typecheck` commit status, and this job
|
|
# skips. See the `typecheck` job comment for the two cases it still covers and
|
|
# how each was measured.
|
|
#
|
|
# 🔴 Read this before treating the fork case as covered. The job carries no
|
|
# secret and is not gated by us on the PR's origin — but GitHub gates it for us,
|
|
# and the practical outcome is that FORK PRs GET NO TYPECHECK AT ALL TODAY.
|
|
# Measured 2026-08-07 over all 16 fork-originated PRs among the ~500 most recent
|
|
# PRs: 11 have a retained `Lint` run and ALL ELEVEN sit at
|
|
# `conclusion=action_required` (awaiting maintainer approval, zero repository
|
|
# code executed); the other 5 are closed PRs whose runs have aged out. `gh pr
|
|
# checks` on the open ones reports "no checks reported". Not one fork PR has ever
|
|
# produced a Typecheck result.
|
|
#
|
|
# Two earlier revisions of this comment got this wrong in opposite directions —
|
|
# first "typecheck is skipped on fork PRs", then (correcting that) "it runs on
|
|
# fork PRs TOO". Both were reasoning from the workflow's CONFIGURATION, which
|
|
# says nothing about whether the job RUNS. The configuration genuinely does not
|
|
# gate on origin; the approval policy does. Keep the job — it is where fork
|
|
# coverage will land the moment a maintainer approves a run, and the sandbox is
|
|
# GitHub's rather than ours — but do not cite it as active fork protection.
|
|
#
|
|
# ESLint and Prettier are split by how the PR touched the file:
|
|
#
|
|
# ADDED files -> BLOCKING. A new file has no pre-existing findings by
|
|
# definition, so this is free, and it stops the backlog
|
|
# from growing while the full flip waits.
|
|
# MODIFIED/RENAMED -> report-only (`continue-on-error`). For a formatter,
|
|
# "changed file" means the WHOLE file, not the changed
|
|
# lines: 789 of 4,116 src files fail `prettier --check`
|
|
# today. Across the 98 src files touched by the last 30
|
|
# commits, 39 fail Prettier and 10 carry a pre-existing
|
|
# ESLint error - 44 (45%) would red the job for reasons
|
|
# unrelated to the PR, turning a 3-line bugfix into a
|
|
# 200-line reformat. The job would be off within a week.
|
|
#
|
|
# Findings surface as file annotations on the PR diff: ESLint via the problem
|
|
# matcher in .github/problem-matchers/, Prettier via ::warning / ::error commands.
|
|
# Report-only steps still show a failed-but-ignored marker in the checks list.
|
|
#
|
|
# The modified-file steps flip to blocking once the backlog is cleared, planned to
|
|
# ride along with the Prettier 2->3 upgrade (which forces a reformat anyway).
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main, release]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: lint-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
eslint:
|
|
name: ESLint + Prettier (changed files)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: pnpm
|
|
|
|
- name: Install
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Collect changed files
|
|
id: changed
|
|
env:
|
|
BASE_REF: ${{ github.base_ref }}
|
|
run: |
|
|
set -euo pipefail
|
|
git fetch -q origin "$BASE_REF"
|
|
git diff --name-only --diff-filter=A FETCH_HEAD...HEAD -- '*.ts' '*.tsx' > added.txt
|
|
git diff --name-only --diff-filter=CMR FETCH_HEAD...HEAD -- '*.ts' '*.tsx' > modified.txt
|
|
# Only paths an eslint config covers. apps/event-engine has its own `root: true`
|
|
# config, which eslint resolves per file, so linting it from here works; the other
|
|
# apps/ members have none and would fall through to the root Next config, whose
|
|
# typed rules crash outside src/.
|
|
LINTABLE='^(src|packages|apps/event-engine)/'
|
|
grep -E "$LINTABLE" added.txt > added-lint.txt || true
|
|
grep -E "$LINTABLE" modified.txt > modified-lint.txt || true
|
|
echo "added ($(wc -l < added.txt)):"; cat added.txt
|
|
echo "modified ($(wc -l < modified.txt)):"; cat modified.txt
|
|
|
|
# BLOCKING. Migrations live next to the schema in packages/civitai-db-schema.
|
|
# The root prisma/migrations directory predates the monorepo and has no
|
|
# migration_lock.toml, so Prisma never reads it — 68 migrations were written
|
|
# there by hand and silently ignored. `pnpm db:migrate:empty` now targets the
|
|
# right path; this catches the hand-authored case it can't.
|
|
- name: Migrations are in the schema package
|
|
run: |
|
|
set -euo pipefail
|
|
STRAYS=$(git diff --name-only --diff-filter=A FETCH_HEAD...HEAD -- 'prisma/migrations/**' || true)
|
|
if [ -n "$STRAYS" ]; then
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
echo "::error file=$f::Migrations belong in packages/civitai-db-schema/prisma/migrations/. Prisma does not read prisma/migrations/ at the repo root. Use: pnpm run db:migrate:empty \"description\""
|
|
done <<< "$STRAYS"
|
|
exit 1
|
|
fi
|
|
echo "no migrations added outside the schema package"
|
|
|
|
- name: Register ESLint problem matcher
|
|
run: echo "::add-matcher::.github/problem-matchers/eslint-unix.json"
|
|
|
|
# BLOCKING. New files start clean, so holding them to the rules costs nothing.
|
|
# Errors only (no --max-warnings): the repo has 3,470 warnings and 1,762 uses of
|
|
# `any`, so failing new files on warnings would hold them to a stricter bar than
|
|
# anything already merged. Warnings still annotate.
|
|
- name: ESLint (added files)
|
|
run: |
|
|
set -euo pipefail
|
|
if [ ! -s added-lint.txt ]; then echo "no new lintable files"; exit 0; fi
|
|
xargs -a added-lint.txt pnpm exec eslint --format unix \
|
|
| sed "s|^$GITHUB_WORKSPACE/||"
|
|
|
|
- name: Prettier (added files)
|
|
run: |
|
|
set -euo pipefail
|
|
if [ ! -s added.txt ]; then echo "no new files"; exit 0; fi
|
|
xargs -a added.txt pnpm exec prettier --list-different > unformatted-added.txt || true
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
echo "::error file=$f::Not formatted. Run: pnpm exec prettier --write $f"
|
|
done < unformatted-added.txt
|
|
if [ -s unformatted-added.txt ]; then
|
|
echo "$(wc -l < unformatted-added.txt) new file(s) are not formatted."
|
|
exit 1
|
|
fi
|
|
echo "all new files formatted"
|
|
|
|
# Report-only: 10% of recently-touched files carry a pre-existing
|
|
# error-severity finding. See the header comment.
|
|
- name: ESLint (modified files, report-only)
|
|
continue-on-error: true
|
|
run: |
|
|
set -euo pipefail
|
|
if [ ! -s modified-lint.txt ]; then echo "no modified lintable files"; exit 0; fi
|
|
xargs -a modified-lint.txt pnpm exec eslint --format unix \
|
|
| sed "s|^$GITHUB_WORKSPACE/||"
|
|
|
|
# Report-only: 19% of src files are unformatted today and a formatter has no
|
|
# changed-line granularity. See the header comment.
|
|
- name: Prettier (modified files, report-only)
|
|
continue-on-error: true
|
|
run: |
|
|
set -euo pipefail
|
|
if [ ! -s modified.txt ]; then echo "no modified files"; exit 0; fi
|
|
xargs -a modified.txt pnpm exec prettier --list-different > unformatted.txt || true
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
echo "::warning file=$f::Not formatted (pre-existing, not blocking). Run: pnpm exec prettier --write $f"
|
|
done < unformatted.txt
|
|
if [ -s unformatted.txt ]; then
|
|
echo "$(wc -l < unformatted.txt) modified file(s) are not formatted."
|
|
exit 1
|
|
fi
|
|
echo "all modified files formatted"
|
|
|
|
typecheck:
|
|
name: Typecheck (fork PRs / non-main base)
|
|
runs-on: ubuntu-latest
|
|
# 🔴 NARROWED, not retired (talos-infra #855, stage 1). The in-cluster Tekton
|
|
# `pr-check` pipeline runs `tsc --noEmit` on every PR and posts the result as
|
|
# the `tekton / typecheck` commit status. Running this job as well typechecks
|
|
# the same tree a second time on every internal PR. Tekton is the one that
|
|
# kept reporting through the 2026-08-06 Actions `major_outage` — it POLLS the
|
|
# PR API on a 1m interval rather than taking a webhook, and the incident
|
|
# degraded webhook delivery.
|
|
#
|
|
# This `if:` is the complement of what Tekton covers. Both disjuncts were
|
|
# measured on 2026-08-07, not assumed:
|
|
#
|
|
# fork PR — the Tekton side gates fork-authored PRs away by author
|
|
# association, deliberately: it executes PR code on our own
|
|
# infrastructure, and GitHub's untrusted-code sandbox is doing work we
|
|
# would otherwise have to build. So this job is the only typecheck a fork
|
|
# PR can get. ⚠️ In practice this arm is DORMANT — see the header.
|
|
#
|
|
# base != main — the Tekton side only watches PRs targeting `main`, so a
|
|
# PR targeting `release` gets no check from it at all. Rare but real: 4
|
|
# PRs have ever targeted `release` (0 of the last 200 merged) — most
|
|
# recent #2582, closed 2026-06-16; last merged #2248, 2026-05-09. Rare is
|
|
# what makes this cheap, not a reason to drop it.
|
|
#
|
|
# Deleting either disjunct silently removes the only typecheck on that path.
|
|
# The honest way to retire this job entirely is to widen Tekton's coverage
|
|
# first and prove it on a real PR of each shape.
|
|
#
|
|
# The name changed with the gate so the checks list explains its own absence.
|
|
# Safe: neither `main` nor `release` has any required_status_checks (verified
|
|
# 2026-08-07 via the branch-protection API), so no merge gate keys on the old
|
|
# name.
|
|
if: >-
|
|
github.event.pull_request.head.repo.full_name != github.repository
|
|
|| github.event.pull_request.base.ref != 'main'
|
|
# event-engine-common is public and fetched over HTTPS, so this needs no
|
|
# secret — which is why the ssh-agent step is gone rather than merely unused.
|
|
# Leaving it in would have kept the fork case broken AND masked the HTTPS
|
|
# path: webfactory/ssh-agent rewrites
|
|
# url."git@key-<sha>.github.com:<owner>/<repo>".insteadOf "https://github.com/<owner>/<repo>"
|
|
# for any loaded key whose comment names a github.com repo, silently sending
|
|
# the new HTTPS URL back over SSH.
|
|
timeout-minutes: 25
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: true
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: pnpm
|
|
|
|
- name: Install
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# Must stay `pnpm run typecheck`, not a bare `tsc --noEmit`. The script is
|
|
# a wrapper (scripts/typecheck.mjs) that sets the V8 heap cap AND
|
|
# classifies the outcome: a `tsc` that dies of heap exhaustion emits zero
|
|
# diagnostics, so a raw invocation produces an empty log that reads as a
|
|
# clean pass to anyone scanning it. The wrapper says CRASHED instead.
|
|
- name: Typecheck
|
|
run: pnpm run typecheck
|
|
|
|
unit:
|
|
name: Unit tests
|
|
runs-on: ubuntu-latest
|
|
# ~8,000 tests that until now only ran on whoever remembered to run them.
|
|
# Node env, no browser — component tests (`*.browser.test.tsx`) are not here
|
|
# on purpose: they need Chromium, which this job does not install. That is
|
|
# the whole reason. This comment used to add "and carry the cold-optimizeDeps
|
|
# flake documented at vitest.config.mts:98-124", which was wrong: those lines
|
|
# document the FIX for that flake, not an open one. vitest.config.mts:109-127
|
|
# dedupes React and pre-bundles `vitest-browser-react` + the JSX runtimes so
|
|
# the optimize pass happens BEFORE the run instead of reloading mid-run, and
|
|
# calls itself the canonical fix; 98-102 is a separate hookTimeout fix.
|
|
# Don't cite a cold-cache flake as a reason to keep this job Chromium-free.
|
|
#
|
|
# REPORT-ONLY to start, deliberately. A full local run passed 8,937/8,943 but
|
|
# timed out 5 tests under machine load; every one of them passed in isolation
|
|
# (that file runs in 138ms — the cost is the ~33s import/transform phase, not
|
|
# the assertions). The per-test timeout is 60s, so a 2-core runner could trip
|
|
# the same way. Blocking on that from day one would red unrelated PRs and the
|
|
# job would be switched off within a week — the same reasoning the ESLint and
|
|
# Prettier modified-file steps are built on.
|
|
#
|
|
# FLIP TO BLOCKING once a couple of weeks of runs show a clean pass rate.
|
|
# Remove `continue-on-error` and this comment together.
|
|
#
|
|
# WHERE THAT STANDS, measured 2026-08-20 so the next reader does not repeat the
|
|
# archaeology. Over the 39 most recent completed runs the `Unit tests` STEP was red
|
|
# in 8, across 8 DISTINCT branches — which reads as flake but was not: every one
|
|
# predates #4191, which fixed a ledger test that had been TRUNK red and therefore
|
|
# failed on every PR regardless of its contents. Of the runs started after that
|
|
# merge, 5 of 5 are green across 4 branches. Encouraging, and far too short a
|
|
# window to act on — 36 minutes is not "a couple of weeks".
|
|
#
|
|
# Two things to know before re-measuring. The run-level `conclusion` is USELESS
|
|
# here: `continue-on-error` makes it `success` while the step underneath is
|
|
# `failure`, so read `steps[].conclusion` from the jobs endpoint, not the run.
|
|
# And this workflow is `pull_request`-only, so the suite never runs against `main`
|
|
# itself — a trunk-red test shows up as every PR going red at once, which is the
|
|
# signature to look for and exactly what the 8 above turned out to be.
|
|
continue-on-error: true
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: true
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: pnpm
|
|
|
|
- name: Install
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Unit tests
|
|
run: pnpm run test:unit:run
|
|
|
|
packages:
|
|
name: Package unit tests
|
|
runs-on: ubuntu-latest
|
|
# The nine `packages/*` suites — 616 tests that, until this job existed, NO CI job ran.
|
|
# The `unit` job above runs `vitest run --project unit`, whose `include` is root-relative
|
|
# (`src/**`, `scripts/**`), so nothing in this repo ever invoked a workspace package's
|
|
# suite. That is not a small gap: the schema-drift detector shipped 81 tests into it,
|
|
# and two of them had been RED on `main` since #3592 corrected the schema out from under
|
|
# their assertions, with nothing to say so.
|
|
#
|
|
# NOT `continue-on-error`, unlike `unit`. Be precise about what that does and does not
|
|
# buy: `main` has branch protection but NO required_status_checks, so a red check here
|
|
# does not prevent a merge. The difference from `unit` is that this renders RED rather
|
|
# than red-but-ignored — a signal someone has to look at and dismiss, not one the UI
|
|
# hides. Making it an actual interlock means adding "Package unit tests" to the required
|
|
# checks, which is a repo-settings change, not a workflow one.
|
|
#
|
|
# The reasoning that made `unit` report-only still does not transfer, which is why this
|
|
# one is not marked ignorable: `unit` is ~12,000 tests whose cost is a ~33s
|
|
# import/transform phase per file, and it timed out five tests under load. This is 616
|
|
# tests with no browser, no database and no Next module graph — ~15s locally.
|
|
#
|
|
# DB-backed tiers self-skip: `@civitai/db-queries` sources DATABASE_URL from the root
|
|
# `.env`, which does not exist on a runner, so its 8 DB-backed tests across three files
|
|
# (6 in tag.db.explain, 1 in model.db.explain, 1 in enum-array-parsers.explain) report
|
|
# as skipped. They are visible as skips in the summary rather than as a silent absence.
|
|
# Those EXPLAIN checks therefore have NEVER run in CI — they are laptop-only evidence.
|
|
# The same is true of the 16-case Kysely/Prisma parity suite, which lives in the app's
|
|
# `unit` project and self-skips without KYSELY_PARITY_DATABASE_URL.
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: true
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: pnpm
|
|
|
|
- name: Install
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# Not a package suite, and deliberately parked in this job anyway: it needs a completed
|
|
# `pnpm install` and a job that FAILS the build, and this is the cheapest one that is
|
|
# both. Adding a fourth job would buy a tidier name for the price of a fourth full
|
|
# install; the step name is what GitHub shows on a failure, so blame stays readable.
|
|
#
|
|
# What it guards: `/api/og` returned 500 on every request for two days because Next
|
|
# 16.3.0 leaves the libvips SVG loader blocked and `next/og` rasterizes through it. We
|
|
# carry the upstream one-line fix as a pnpm patch. The behavioural regression test for
|
|
# that outage lives in the `unit` job above — which is `continue-on-error`, so it cannot
|
|
# stop the same breakage reaching production a second time. This runs the structural
|
|
# half of it, against the INSTALLED node_modules rather than the patch file, so a patch
|
|
# that exists but did not apply is caught. ~50ms, no test runner.
|
|
- name: Next SVG loader patch applied
|
|
run: node scripts/ci/assert-next-svg-patch-applied.mjs
|
|
|
|
- name: Package unit tests
|
|
run: pnpm run test:packages:run --reporter=default --reporter=json --outputFile=packages-report.json
|
|
|
|
# A green vitest run is a claim, not evidence: `--project` matching nothing exits 0,
|
|
# and so does a config whose globs stopped resolving. This asserts the LEDGER — every
|
|
# workspace package with a vitest config and a test file on disk must appear in the
|
|
# results — so the job fails when the executed set shrinks, which is the regression the
|
|
# totals cannot see.
|
|
- name: Assert every package suite actually ran
|
|
if: always()
|
|
run: node scripts/ci/assert-workspace-suites-ran.mjs packages-report.json packages
|
|
|
|
apps:
|
|
# Named for BOTH things it runs. It used to be "App unit tests", and the typecheck steps
|
|
# were added under that name — so a type error in an app would have rendered in the checks
|
|
# list as a failing unit-test job, pointing the reader at the wrong suite. Safe to rename:
|
|
# neither `main` nor `release` has any required_status_checks (re-verified 2026-08-20 via
|
|
# the branch-protection API, not inherited from the earlier note), so no merge gate keys
|
|
# on the old string.
|
|
name: App unit tests + typecheck
|
|
runs-on: ubuntu-latest
|
|
# The `apps/*` suites — the same gap as `packages` above, in the sibling directory, and
|
|
# missed when that one was closed: the root config registered `packages/*/vitest.config.*`
|
|
# as projects and nothing for `apps/*`, so 369 tests across five apps had never once run
|
|
# in CI. They only ever ran for whoever remembered `pnpm --filter <app> test` by hand.
|
|
#
|
|
# A SEPARATE job rather than folding the apps into `packages`, because the two selectors
|
|
# have to stay disjoint anyway (see the `apps/*` note in vitest.config.mts) and separate
|
|
# jobs keep the blame attributable: a red app suite should not make the package suites
|
|
# look broken. Costs one more `pnpm install`; these suites run in seconds.
|
|
#
|
|
# Not `continue-on-error`, for the same reason as `packages`: no browser, no database, no
|
|
# Next module graph, ~7s locally. As there, RED here does not block a merge — `main` has
|
|
# no required_status_checks — it is a signal someone has to look at and dismiss.
|
|
#
|
|
# Selection is by vitest config, so an app without one is silently not covered here. Every app
|
|
# under `apps/` has one today; scripts/ci/assert-workspace-suites-ran.mjs is what notices if that
|
|
# stops being true.
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: true
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: pnpm
|
|
|
|
- name: Install
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: App unit tests
|
|
run: pnpm run test:apps:run --reporter=default --reporter=json --outputFile=apps-report.json
|
|
|
|
# Same positive control as the packages job, and it earns its keep harder here: these
|
|
# projects are named by hand (`app:auth`, ...), so an app that loses its `test.name`
|
|
# stops being selected while the other four keep the run green and exit 0. Measured,
|
|
# not assumed — see the script header.
|
|
- name: Assert every app suite actually ran
|
|
if: always()
|
|
run: node scripts/ci/assert-workspace-suites-ran.mjs apps-report.json apps
|
|
|
|
# ── App typechecks ──────────────────────────────────────────────────
|
|
# No app under `apps/` was typechecked by anything before this. The root
|
|
# `pnpm run typecheck` is bounded by the root tsconfig `include` — `src`,
|
|
# `packages/*/src`, `scripts/local-dev/*.ts`, `tests`, `test`,
|
|
# `.next/types/**/*.ts` — with no `apps/*` entry, and BOTH tiers (this
|
|
# workflow's `typecheck` job and the `tekton / typecheck` status) run that
|
|
# same script, so the gap could not close on its own.
|
|
#
|
|
# Parked in this job rather than its own: `pnpm install` is the expensive
|
|
# part and this job has already paid for it. The tradeoff is that a type
|
|
# error renders under this job's name — hence the job rename above.
|
|
#
|
|
# Deliberately ONE step driving a script, not one `pnpm --filter` step per
|
|
# app. Per-app steps abort at the first red app, so a shared type change
|
|
# that breaks several of them reports one; and `pnpm --filter <name>` EXITS
|
|
# 0 WHEN THE NAME MATCHES NOTHING (measured, pnpm 10.28.1 — it prints "No
|
|
# projects matched the filters" and succeeds), so a hardcoded list of
|
|
# package names is one chance per app for a rename to turn this gate into a
|
|
# green no-op. The script derives the app set from disk, proves each filter
|
|
# selected a real package, and collects every failure. See its header.
|
|
- name: Typecheck apps
|
|
run: node scripts/ci/typecheck-apps.mjs
|