From 3d828eaadd31a6d158441832483baddd835c64cc Mon Sep 17 00:00:00 2001 From: Zachary Lowden Date: Wed, 19 Aug 2026 21:18:04 -0500 Subject: [PATCH] ci: validate the default Windows contributor setup, non-blocking (#4162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every job in this repo runs on ubuntu-latest, so nothing validates Windows — while Windows support is real and maintained: defender-exclusions.ps1, win32 branches in test-unit-run.mjs / bench.mjs / console.mjs / worktree.mjs, and it.skipIf(win32) guards in scripts/__tests__. A supported platform verified by nothing is how a contributor's first hour goes to a break nobody else can reproduce. The specific exposure this targets: `pnpm install` runs `preinstall` (npx only-allow pnpm) and `postinstall` (pnpm run db:generate). The Makefile carried `# TODO fix postinstall on git bash` against that path — and the target it sat on was DEAD for everyone, because `npm i` exits 1 under only-allow. So the hook path has gone a long time unexercised on Windows. Un-breaking the target (4107) means the next Windows contributor is the first to walk it in a while. This walks it first. Both shells on purpose. pwsh is the Windows default; bash is Git Bash, and the TODO was Git-Bash-specific. A pwsh-only job would report green over the single case we have written evidence about. Non-blocking (continue-on-error) to start. A gate that lands red on day one and stops unrelated PRs trains everyone to click through, which is worse than no gate. Flip it once it has been green long enough to mean something. Does NOT start the compose stack: those are Linux containers and Docker on Windows runners is slow and flaky — noise, not signal. Everything up to `pnpm dev` is covered; the services half is Linux-identical and already exercised by the ubuntu jobs. Costs nothing: windows-latest is a standard GitHub-hosted runner, free for public repositories. The install step carries a positive control, because `pnpm install` exiting 0 is not evidence the postinstall hook ran — a hook that silently no-ops also exits 0, and the missing client then surfaces much later as a confusing runtime error. The job asserts packages/civitai-db-schema/prisma/schema.prisma exists afterwards. That file is a genuine control precisely because it is GITIGNORED: it cannot arrive from the checkout, so its presence proves the hook executed on this runner. The guard was tested against its own failure modes before being trusted, rather than assumed to work: artifact absent -> rc=1, "postinstall did not run" artifact 3 lines -> rc=1, "slim schema looks truncated" artifact 200 lines -> rc=0 Each failure fires for its OWN reason with a distinct title, so a red run names which thing broke. The 50-line floor sits against a source schema of 8,087 lines — two orders of magnitude of headroom, so ordinary churn cannot trip it while a stub still fails. Also restores the git-bash TODO to the Makefile rather than leaving it deleted. Nothing has verified it either way; dropping the note would have retired the only written record of a known hazard on a path that had stopped being walked. It now points at this workflow as the thing that will answer it. Co-authored-by: Claude Opus 5 (1M context) --- .github/workflows/windows-dev-env.yml | 128 ++++++++++++++++++++++++++ Makefile | 12 +++ 2 files changed, 140 insertions(+) create mode 100644 .github/workflows/windows-dev-env.yml diff --git a/.github/workflows/windows-dev-env.yml b/.github/workflows/windows-dev-env.yml new file mode 100644 index 0000000000..154aca89b3 --- /dev/null +++ b/.github/workflows/windows-dev-env.yml @@ -0,0 +1,128 @@ +name: Windows dev-env smoke + +# Non-blocking canary for the DEFAULT (non-Nix) contributor setup on native Windows. +# +# WHY: every other job in this repo runs on ubuntu-latest, so nothing validates +# Windows — while Windows support is real and actively maintained: +# `.claude/skills/dev-server/scripts/defender-exclusions.ps1`, `win32` branches in +# `scripts/test-unit-run.mjs`, `scripts/test-perf/bench.mjs`, +# `.claude/skills/dev-server/console.mjs` and `.../scripts/worktree.mjs`, plus +# `it.skipIf(process.platform === 'win32')` guards in `scripts/__tests__/`. +# A supported platform verified by nothing is how a contributor's first hour gets +# spent on a break nobody else can reproduce. +# +# THE SPECIFIC EXPOSURE this is aimed at: `pnpm install` runs two lifecycle hooks — +# `preinstall` (`npx only-allow pnpm`) and `postinstall` (`pnpm run db:generate`, +# i.e. `node scripts/generate-slim-schema.js && prisma generate --no-hints`). The +# Makefile carried `# TODO fix postinstall on git bash` against exactly that path. +# That target was ALSO dead for everyone — `npm i` exits 1 under `only-allow` — so +# the hook path went a long time without being exercised on Windows by anybody. +# Un-breaking the target means the next Windows contributor is the first to walk +# it in a long while. This job walks it first. +# +# BOTH SHELLS ON PURPOSE: `pwsh` is the Windows default, `bash` is Git Bash — and +# the TODO above was Git-Bash-specific. A pwsh-only job would not reproduce it, and +# would report green over the one case we have written evidence about. +# +# DELIBERATELY NON-BLOCKING (`continue-on-error: true`) to start. A gate that lands +# red on day one and stops unrelated PRs trains everyone to click through, which is +# strictly worse than no gate. Turn it off once this has been green long enough to +# mean something. +# +# DELIBERATELY NOT starting the compose stack: those are Linux containers, and +# Docker on Windows runners is slow and flaky — it would buy noise, not signal. +# This covers everything up to `pnpm dev`; the services half is Linux-identical +# and already exercised by the ubuntu jobs. +# +# COST: `windows-latest` is a standard GitHub-hosted runner, which is free for +# public repositories. This adds no Actions minutes. + +on: + pull_request: + branches: [main, release] + +permissions: + contents: read + +concurrency: + group: windows-dev-env-${{ github.ref }} + cancel-in-progress: true + +jobs: + smoke: + name: Windows dev-env (${{ matrix.shell }}) + runs-on: windows-latest + continue-on-error: true + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + shell: [pwsh, bash] + defaults: + run: + shell: ${{ matrix.shell }} + + steps: + # `submodules: true` is CI's equivalent of the documented + # `git submodule update --init event-engine-common`. Without it the install + # fails in a way that looks like a code defect rather than a missing checkout + # step — the exact confusion that cost a session earlier. + - uses: actions/checkout@v4 + with: + submodules: true + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: pnpm + + # Report rather than assume. `engines.node` is ADVISORY — pnpm prints + # `WARN Unsupported engine` and exits 0 on a wrong major — so a mismatch here + # would otherwise surface later as unrelated-looking failures. + - name: Toolchain actually in use + run: | + node --version + pnpm --version + + # THE STEP UNDER TEST. Both lifecycle hooks run here. + - name: pnpm install (runs preinstall + postinstall) + run: pnpm install --frozen-lockfile + + # POSITIVE CONTROL — the half that makes the green mean something. + # + # `pnpm install` exiting 0 is NOT proof the postinstall hook did its job: a + # hook that silently no-ops also exits 0, and the missing client then surfaces + # much later as a confusing runtime error. So assert the artifact. + # + # This particular artifact is a genuine control because + # `packages/civitai-db-schema/prisma/schema.prisma` is GITIGNORED — it cannot + # arrive from the checkout, so its presence proves the hook executed here. + # + # Runs under `node` rather than the matrix shell on purpose: this checks an + # artifact, not shell behaviour, and it keeps the assertion free of pwsh-vs-bash + # quoting differences. + - name: Assert postinstall actually generated the slim schema + shell: node {0} + run: | + const fs = require('fs'); + const f = 'packages/civitai-db-schema/prisma/schema.prisma'; + if (!fs.existsSync(f)) { + console.log(`::error title=postinstall did not run::${f} is absent after \`pnpm install\`. It is gitignored, so it can only come from the postinstall hook (\`pnpm run db:generate\`). The install reported success, so the hook failed silently or was skipped.`); + process.exit(1); + } + const lines = fs.readFileSync(f, 'utf8').split('\n').length; + console.log(`${f}: ${lines} lines`); + // A truncated or empty generate also exits 0. 50 is far below any real + // schema and far above an accidental stub, so it separates the two + // without pinning a number that ordinary schema churn would trip. + if (lines < 50) { + console.log(`::error title=slim schema looks truncated::${f} exists but has only ${lines} lines.`); + process.exit(1); + } + + # Standalone re-run: the hook working once inside `pnpm install` does not mean + # a contributor can run it by hand, which the docs tell them to do. + - name: db:generate standalone + run: pnpm run db:generate diff --git a/Makefile b/Makefile index 573dc7c774..2ccd5f4c64 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,18 @@ copy-env: # `npm i` cannot work here: package.json's `preinstall` runs `npx only-allow pnpm`, # which exits 1 under npm. `make init` was dead on that line. +# +# This line previously carried `# TODO fix postinstall on git bash`. That note is +# preserved rather than dropped, because nothing has verified it either way: the +# target it sat on was DEAD for everyone (see above), so the `postinstall` hook it +# warned about -- `pnpm run db:generate`, i.e. generate-slim-schema.js then +# `prisma generate` -- has not been exercised here in a long time. Un-breaking the +# target does not fix whatever that was; it just means the next Windows contributor +# is the first to walk the path in a while. +# +# `.github/workflows/windows-dev-env.yml` now runs `pnpm install` on windows-latest +# under BOTH pwsh and Git Bash to find out. It is non-blocking. When it has been +# green for a while, delete this note; if it goes red under bash, this is the lead. .PHONY: install install: pnpm install