ci: split the gate into a lint job and an OS matrix - #163
Merged
Conversation
The Windows job added in #159 was a hand-copied duplicate of `check` named after its runner, and the two jobs disagreed about what they were for: one ran the whole gate plus build and test, the other a silent subset with a ten-line comment arguing why. Split by purpose instead of by platform, which is what the ecosystem converges on (of 40 sampled Node/TS repos, 25 of the 31 that test on Windows express it as a matrix over `os`; `test` and `lint` are the two most common job ids by a wide margin). `lint` owns the platform-independent gates — typecheck, lint, format, knip — none of which need a build, which is why they already ran before `pnpm build`. `test` owns build and test across ubuntu-latest and windows-latest, so the subset stops being a special case that needs explaining. Drop the claim that a Windows runner bills at 2x: this repository is public, where standard GitHub-hosted runners are free. The real cost is wall clock — the Windows job runs ~102s against ~70s for Linux and sets the critical path. Add timeout-minutes to both jobs. Neither had one, so a hung run — this suite spawns processes and races for a port — would have burned the 360 minute default before anyone noticed. Set fail-fast: false so one platform failing does not cancel the other; which of the two broke is the finding.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The Windows job added in #159 was a hand-copied duplicate of
check, named after its runner rather than its purpose, and the two jobs disagreed about what they were for: one ran the whole gate plus build and test, the other a silent subset guarded by a ten-line comment arguing why.This splits CI by purpose instead of by platform:
lintubuntu-latesttypecheck,lint,format:check,kniptest${{ matrix.os }}→ubuntu-latest,windows-latestbuild,testOnce the platform-independent gates live in their own job, "Windows deliberately runs less" stops being a special case that needs explaining — both matrix legs run exactly the same steps.
Why this shape
I sampled the workflow files of 40 well-known Node/TS repositories (vite, vitest, rollup, rolldown, oxc, biome, prettier, eslint, pnpm, npm/cli, yarn, vue, svelte, kit, astro, nuxt, next, react-router, storybook, playwright, TypeScript, undici, nitro, changesets, turborepo, jest, webpack, zod, fastify, axios, execa, mcp typescript-sdk, gh cli, deno, nx, tailwindcss, ni, typescript-eslint, express, lodash):
oswith Linux as the base; 8 keep a dedicated Windows job (vuejs/core, oxc, rolldown, deno, jest); 5 pass the OS into a reusable workflow.testappears in 20 repos,lintin 17 — the two most common by a wide margin.lintis the conventional umbrella name for the static gates even when the job also typechecks and formats (vite and vitest both do exactly that).Also fixed
timeout-minutes(10 / 15). Neither job had one. This suite spawns processes and races for a port, so a hung run would have burned the 360-minute default.fail-fast: false, so one platform failing does not cancel the other — which of the two broke is the finding.Considered and rejected
ubuntu-24.04-arm— technically viable (all 18 native binding packages in the lockfile shiplinux-arm64-gnu) and free on public repos, but it saves nothing: total CI wall clock is bounded by the Windows leg, not the Linux one. It would also mean pinning a version manually, since there is noubuntu-latest-armalias.ubuntu-slim— 1 vCPU / 5 GB, container rather than VM, unprivileged, 15-minute job cap, and documented as "not suitable for typical heavyweight CI/CD builds". The repos that use it (vite, vitest, typescript-eslint, TypeScript) reserve it for changed-file gates, spell checks and status aggregation. The three small workflows here already finish in 4-15s, andactionlintcould not move there anyway — it runs adocker://container action, which unprivileged containers do not support.macos-latest— the maintainer develops on macOS, so it is continuously exercised by hand; Windows is the platform no contributor here can run. Worth revisiting if a compiled binary release ever needs to be produced on macOS.Checklist
pnpm typecheck && pnpm lint && pnpm format:check && pnpm knip && pnpm build && pnpm testactionlintpasses on all five workflows