Skip to content

Commit 9211d12

Browse files
wormeymanclaude
andcommitted
ci: add a verify workflow and Renovate config (first .github/ in repo history)
`pnpm run verify` was the whole quality gate and it only ran when a human remembered to, or at deploy time - which can be days after the merge that broke it. `git log --all -- .github` was empty. Closes the structural half of issue #54. The workflow runs `pnpm run verify` verbatim rather than re-listing its three phases, so CI and local cannot drift into two definitions of "consistent". No refs:sync (needs a Factorio binary), no build step, no deploy, no secrets. Third-party actions are pinned to full commit SHAs with the release named in a trailing comment; permissions are `contents: read` only. Renovate over Dependabot because the holds here need encoding, not ignoring: typescript is disabled outright, pako carries a 14-day age and a note pointing at the byte-exactness invariant, wrangler + vitest-pool-workers are grouped because pool-workers hard-pins wrangler, and the brace-expansion override and `engines.node` floor are both marked not-a-stale-version. `minimumReleaseAge` is declared at 3 days specifically so Renovate can never propose a release fresh enough to make pnpm write a `minimumReleaseAgeExclude:` bypass. Config validated with `renovate-config-validator`. Renovate stays inert until the GitHub App is enabled on the repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54
1 parent c303e77 commit 9211d12

2 files changed

Lines changed: 199 additions & 0 deletions

File tree

.github/renovate.json5

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Dependency automation for this repo (issue #54). Renovate, not Dependabot:
2+
// several of this project's dependency decisions are deliberate HOLDS whose
3+
// reasoning lives in CLAUDE.md prose, and Dependabot's `ignore` entries cannot
4+
// express most of them. Everything below exists to stop a bot from cheerfully
5+
// re-proposing, every week, the exact bump this project has already decided
6+
// against - and to make the reason travel with the proposal when there is one.
7+
//
8+
// This file does nothing on its own. Renovate only acts once the Renovate
9+
// GitHub App is enabled for the repository (https://github.com/apps/renovate).
10+
// Validated with `renovate-config-validator` before landing.
11+
//
12+
// THE pnpm INTERACTION, which is the subtle part.
13+
// This workspace relies on pnpm 11's built-in release-age cooldown - there is no
14+
// `minimumReleaseAge` line in pnpm-workspace.yaml, the guard is a pnpm default.
15+
// When pnpm is asked to install something newer than that cooldown allows it
16+
// does not fail; it writes a `minimumReleaseAgeExclude:` bypass into
17+
// pnpm-workspace.yaml, which is how `vue-tsc@3.3.8` once silently waived the
18+
// guard. A dependency bot is a machine for producing exactly that situation.
19+
// So `minimumReleaseAge` is declared HERE, explicitly, at a value comfortably
20+
// above pnpm's default: Renovate must never open a PR for a release pnpm would
21+
// want a bypass for. Declaring it also means a future pnpm changing its default
22+
// cannot move this repo's floor silently.
23+
// If `minimumReleaseAgeExclude:` ever appears in a Renovate PR's diff, that PR
24+
// is wrong - do not merge it, fix the age rule instead.
25+
{
26+
$schema: "https://docs.renovatebot.com/renovate-schema.json",
27+
extends: [
28+
"config:recommended",
29+
// Pin every GitHub Action to a commit SHA rather than a moving tag, and keep
30+
// the `# vX.Y.Z` comment beside it updated. .github/workflows/verify.yml is
31+
// already written that way by hand; this makes it the rule for anything
32+
// added later, so the choice cannot drift action-by-action.
33+
"helpers:pinGitHubActionDigests",
34+
],
35+
36+
// See the pnpm interaction above. This is a floor, not a preference.
37+
minimumReleaseAge: "3 days",
38+
39+
// The central invariant here is byte-exactness against captured fixtures, and
40+
// a green CI run is evidence of consistency, not of a bump being *correct*
41+
// (`pnpm run verify` does not exercise every dependency - see the js-beautify
42+
// note in pnpm-workspace.yaml). Nothing merges unread.
43+
automerge: false,
44+
45+
dependencyDashboard: true,
46+
47+
// One weekly batch instead of a trickle. Times are UTC (no `timezone` set).
48+
schedule: ["before 6am on monday"],
49+
50+
packageRules: [
51+
{
52+
// THE ONE THAT MATTERS MOST. Without it a bot proposes 6.0.3 -> 7.x every
53+
// single week and someone re-derives the refusal every time (see #48, #52).
54+
// TypeScript 7 exposes no programmatic API yet, the official migration is
55+
// a dual-install alias, and vue-tsc/Volar cannot type-check .vue against
56+
// it. Revisit at 7.1 deliberately, by hand.
57+
matchPackageNames: ["typescript"],
58+
enabled: false,
59+
},
60+
{
61+
// The codec's byte-exactness rests on `{ level: 9, legacyHash: true }`.
62+
// `legacyHash` is a pako EXTENSION with no zlib-API contract, from a
63+
// library that already flipped that default inside a single major (2.2.0
64+
// added it defaulting true, 3.0.0 flipped it false). A pako bump is a
65+
// codec change until proven otherwise.
66+
matchPackageNames: ["pako"],
67+
minimumReleaseAge: "14 days",
68+
automerge: false,
69+
prBodyNotes: [
70+
"**Read `src/codec/deflate.ts` and the pako table in CLAUDE.md before merging.** Deflate must stay madler-zlib-compatible at level 9 and byte-exact against all 9 fixtures. `test/deflate.spec.ts` has a block that fails by name if `legacyHash` is dropped, renamed or re-defaulted - do not silence it by editing a fixture.",
71+
],
72+
},
73+
{
74+
// @cloudflare/vitest-pool-workers hard-pins an exact wrangler. Split PRs
75+
// cannot resolve, so these two only ever move together.
76+
matchPackageNames: ["wrangler", "@cloudflare/vitest-pool-workers"],
77+
groupName: "cloudflare worker toolchain",
78+
prBodyNotes: [
79+
"After merging, regenerate the worker types: `pnpm --filter @fmw/preview-worker exec wrangler types && pnpm vp check --fix`. The formatter pass is not optional.",
80+
],
81+
},
82+
{
83+
// `overrides.brace-expansion: 2.1.3` in pnpm-workspace.yaml is a
84+
// deliberate maintenance-backport pin, not a stale version. Renovate would
85+
// read it as three majors behind and propose 5.x, which is a spike into a
86+
// dual-ESM/CJS package consumed by a CJS minimatch - explicitly rejected.
87+
// The advisory's flat `<=5.0.7` range also means `pnpm audit` still flags
88+
// 2.1.3 even though it carries the fix, so "the audit is red" is not a
89+
// reason to take this bump. Read the comment in pnpm-workspace.yaml first.
90+
matchPackageNames: ["brace-expansion"],
91+
matchDepTypes: ["overrides", "pnpm.overrides"],
92+
enabled: false,
93+
},
94+
{
95+
// `engines.node` is a deliberately permissive FLOOR (">=24.18.0"): older
96+
// versions are untested, not known-broken. It is not a version to keep
97+
// current. The version CI and development actually run on is
98+
// `.node-version`, which Renovate may bump (see below).
99+
matchDepTypes: ["engines"],
100+
enabled: false,
101+
},
102+
{
103+
// `.node-version` became machinery, not documentation, the moment
104+
// .github/workflows/verify.yml started feeding it to setup-node. Bumps are
105+
// welcome but never unattended.
106+
matchFileNames: [".node-version"],
107+
prBodyNotes: [
108+
"This changes the Node version **CI** runs on, not just a local hint. Run `pnpm run verify` locally on the proposed version before merging.",
109+
],
110+
},
111+
{
112+
// vite-plus is pre-1.0 and is the entire static-check + test toolchain.
113+
// Its tsgolint engine bumps have twice re-triggered the `TS2321: Excessive
114+
// stack depth ... UserConfig` pathology in vite.config.ts, which looks
115+
// like a type error in this repo and is not one.
116+
matchPackageNames: ["vite-plus"],
117+
prBodyNotes: [
118+
"If `vp check` starts reporting `TS2321: Excessive stack depth comparing types ... 'UserConfig'`, that is the known vite.config.ts comparison-depth pathology, not a real type error - see the type-checking section of CLAUDE.md for the `vue() as Plugin` cast that collapses it.",
119+
],
120+
},
121+
],
122+
}

.github/workflows/verify.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# The whole quality gate, run automatically. Until this file existed, `pnpm run
2+
# verify` only ran when a human remembered to (or at deploy time, which can be
3+
# days after the breaking merge) - see issue #54.
4+
#
5+
# This job deliberately runs `pnpm run verify` VERBATIM rather than re-listing
6+
# its three phases as separate steps. `verify` is the single definition of "the
7+
# repo is consistent"; splitting it here would create a second definition that
8+
# can silently drift from package.json. Note it is `verify`, not `check` -
9+
# `check` is `vp check --fix` and CI must never rewrite files.
10+
#
11+
# What is deliberately NOT here:
12+
# - `pnpm refs:sync`. It needs a Factorio binary and ~/GitHub/factorio-data,
13+
# neither of which exists on a runner. `verify` is designed to pass with no
14+
# Factorio installed and that property is what makes this workflow possible.
15+
# - `pnpm vp build`. Not part of `verify`, and the build stamp reads git
16+
# history, so it wants a checkout policy of its own. Tracked as a follow-up.
17+
# - Any deploy step or credential. Cloudflare Pages does not build this repo
18+
# (`deploy:app` uploads an already-built `dist`), so CI here is a check
19+
# only and the job needs no secrets at all.
20+
name: verify
21+
22+
on:
23+
push:
24+
branches: [main]
25+
pull_request:
26+
# So a run can be forced without an empty commit.
27+
workflow_dispatch:
28+
29+
# Superseded pushes to the same PR are pointless at ~2 minutes a run.
30+
concurrency:
31+
group: verify-${{ github.workflow }}-${{ github.ref }}
32+
cancel-in-progress: true
33+
34+
# Minimum scope: the job only reads the tree. It writes no statuses, comments,
35+
# packages or releases, so nothing beyond `contents: read` is granted.
36+
permissions:
37+
contents: read
38+
39+
jobs:
40+
verify:
41+
runs-on: ubuntu-latest
42+
# `verify` is ~60-90s of work; 15 minutes is a hang detector, not a budget.
43+
timeout-minutes: 15
44+
env:
45+
# `preview:test` shells out to wrangler (`wrangler types --check`). Keep it
46+
# from phoning home for telemetry on a runner where nobody can answer the
47+
# opt-in prompt.
48+
WRANGLER_SEND_METRICS: "false"
49+
steps:
50+
# Third-party actions are pinned to a full commit SHA, never a moving tag.
51+
# The trailing comment names the release each SHA is, and Renovate updates
52+
# the SHA and that comment together - see .github/renovate.json5.
53+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
54+
55+
# No `version:` input on purpose. pnpm/action-setup >= 6 reads
56+
# `devEngines.packageManager` from package.json, so the pnpm pin stays in
57+
# exactly one place. Hard-coding it here would be a second pin to drift.
58+
# This step must precede setup-node: `cache: pnpm` below resolves the
59+
# store path by running pnpm, so pnpm has to be on PATH already.
60+
- uses: pnpm/action-setup@008330803749db0355799c700092d9a85fd074e9 # v6.0.9
61+
62+
# `.node-version` (26.5.0) is the single source of truth for the Node
63+
# version, and this is its first real consumer - the file went from
64+
# documentation to machinery when this workflow landed. `engines.node`
65+
# stays a permissive floor and is deliberately NOT what CI runs.
66+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
67+
with:
68+
node-version-file: .node-version
69+
cache: pnpm
70+
71+
# A full workspace install. Anything narrower can leave a sibling
72+
# workspace's symlink dangling and produce fake
73+
# `TS2307: Cannot find module 'vitest'` errors; a real full install is the
74+
# one that reports `Scope: all 3 workspace projects`.
75+
- run: pnpm install --frozen-lockfile
76+
77+
- run: pnpm run verify

0 commit comments

Comments
 (0)