From 3bab95acd557567d03680da0f739691585887b7e Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Sat, 15 Aug 2026 01:35:25 +0200 Subject: [PATCH] fix: copy serve.ts into the Docker build stage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The image build ran the tests without serve.ts present, so the server the tests spawn never started and the hook stalled until it timed out. The runtime stage would have failed on the same missing file immediately after. The test now bails the moment the process exits and reports its stderr, so this shows up as "serve.ts exited with 1 before listening" in 53ms rather than an unexplained hook timeout after five seconds. serve.ts is also in tsconfig's include now — it was outside it, so a type error in the file that serves the app would only have surfaced at runtime. Adding it immediately caught one in the test. Verified by replaying the build stage against exactly the copied file set. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 23 ++++++++++++++++------- Dockerfile | 2 +- README.md | 10 ++++++++-- test/serve.test.ts | 23 ++++++++++++++++++----- tsconfig.json | 2 +- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d29640..1427653 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,11 +114,26 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max + # Deploying to Pages needs two one-time steps that CI cannot do for itself: + # + # 1. Settings → Pages → Source: "GitHub Actions". + # The default GITHUB_TOKEN cannot do this. Creating a Pages site needs + # `administration: write`, which is not a permission a workflow can grant + # GITHUB_TOKEN, so `configure-pages` with enablement: true fails with + # "Resource not accessible by integration". + # 2. Settings → Secrets and variables → Actions → Variables: + # set DEPLOY_PAGES to "true". + # + # Gated on that variable so the pipeline stays green for anyone who does not + # want Pages, rather than failing on every push forever. pages: name: Deploy to Pages runs-on: ubuntu-latest needs: build - if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + if: >- + github.ref == 'refs/heads/main' && + github.event_name != 'pull_request' && + vars.DEPLOY_PAGES == 'true' permissions: pages: write id-token: write @@ -131,12 +146,6 @@ jobs: name: site path: public - uses: actions/configure-pages@v5 - with: - # Turn Pages on rather than requiring someone to click through - # repository settings first. Needs the pages: write permission above. - # If this still fails, Pages is unavailable for the repository — - # private repos on the free plan cannot use it; delete this job. - enablement: true - uses: actions/upload-pages-artifact@v3 with: path: public diff --git a/Dockerfile b/Dockerfile index 4e76691..98476ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile -COPY tsconfig.json index.html ./ +COPY tsconfig.json index.html serve.ts ./ COPY src ./src COPY scripts ./scripts COPY fixtures ./fixtures diff --git a/README.md b/README.md index f019ccf..3aa7362 100644 --- a/README.md +++ b/README.md @@ -153,8 +153,14 @@ so iteration never re-fetches. Every event links back to its source. Both `.github/workflows/ci.yml` and `.gitlab-ci.yml` run the same gates on every push — typecheck, tests, and a feed sanity check — then build and publish a container image from the default branch. -GitHub Actions additionally deploys to Pages, enabling it on first run. Pages is unavailable for -private repositories on the free plan — delete the `pages` job if that applies. +GitHub Actions can also deploy to Pages, but that needs two one-time steps it cannot do for itself — +the default `GITHUB_TOKEN` is not allowed to create a Pages site: + +1. **Settings → Pages → Source: GitHub Actions.** +2. **Settings → Secrets and variables → Actions → Variables:** add `DEPLOY_PAGES` = `true`. + +Until then the `pages` job is skipped and the pipeline stays green. Pages is unavailable for private +repositories on the free plan. The feed job fails if the event count collapses. A source that quietly stops yielding events is the failure mode a parser-only pipeline is most prone to, and nothing else would surface it. Everything diff --git a/test/serve.test.ts b/test/serve.test.ts index f64320f..4df53f0 100644 --- a/test/serve.test.ts +++ b/test/serve.test.ts @@ -33,19 +33,32 @@ beforeAll(async () => { proc = Bun.spawn(["bun", "run", "serve.ts"], { env: { ...process.env, PORT: String(port), PUBLIC_DIR: root }, stdout: "ignore", - stderr: "ignore", + stderr: "pipe", }); - for (let i = 0; i < 60; i += 1) { + // Bail as soon as the process dies rather than retrying against a corpse: + // a missing serve.ts otherwise shows up only as "a hook timed out", which + // says nothing about the cause. + for (let i = 0; i < 40; i += 1) { + if (proc.exitCode !== null) { + const stderr = proc.stderr; + const why = + stderr instanceof ReadableStream + ? await new Response(stderr).text() + : "(no stderr captured)"; + throw new Error( + `serve.ts exited with ${proc.exitCode} before listening:\n${why.slice(0, 500)}`, + ); + } try { await fetch(`${base}/api/health`); return; } catch { - await Bun.sleep(100); + await Bun.sleep(50); } } - throw new Error("server did not start"); -}); + throw new Error(`server did not listen on ${base} within 2s`); +}, 10_000); afterAll(async () => { proc.kill(); diff --git a/tsconfig.json b/tsconfig.json index 2009292..d5ac3ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,5 +16,5 @@ "jsx": "react-jsx", "verbatimModuleSyntax": true }, - "include": ["src", "test", "scripts"] + "include": ["src", "test", "scripts", "serve.ts"] }