diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7bb293c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,54 @@ +name: Build + +# Railway deploys main itself, through its GitHub integration. This workflow checks that +# every pull request and every push to main builds. +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +# A newer push supersedes an in-flight check on the same branch. +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + # Railway builds from the Dockerfile too, but a red check here points at the actual + # error rather than at a remote build log. + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: npm + + - run: npm ci + + # The route table and search index are generated and gitignored, so they must exist + # before tsc runs — a fresh checkout has neither. + - name: Generate routes and search index + run: npm run gen + + - name: Typecheck + run: npx tsc --noEmit + + - name: Heading anchors match the previously published site + run: node scripts/test-slug.mjs + + - name: Build + run: npm run build + + - name: Every page prerendered + run: | + count=$(find dist -name index.html | wc -l) + echo "prerendered $count pages" + test "$count" -ge 35 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 33a895c..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,133 +0,0 @@ -name: Deploy - -# Pushes to main are deployed to Railway. Pull requests get the build check only, so a -# broken branch cannot reach production. -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -# One deploy at a time. A newer push supersedes an in-flight PR check, but never an -# in-flight deploy — cancelling mid-deploy could leave a half-rolled revision. -concurrency: - group: deploy-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -permissions: - contents: read - -jobs: - # Fails fast on a broken build before anything is uploaded. Railway rebuilds from the - # Dockerfile anyway, but a red check here points at the actual error rather than at a - # remote build log. - build: - name: Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - - uses: actions/setup-node@v7 - with: - node-version: 22 - cache: npm - - - run: npm ci - - # The route table and search index are generated and gitignored, so they must exist - # before tsc runs — a fresh checkout has neither. - - name: Generate routes and search index - run: npm run gen - - - name: Typecheck - run: npx tsc --noEmit - - - name: Heading anchors match the previously published site - run: node scripts/test-slug.mjs - - - name: Build - run: npm run build - - - name: Every page prerendered - run: | - count=$(find dist -name index.html | wc -l) - echo "prerendered $count pages" - test "$count" -ge 35 - - # Handed to the deploy job so the smoke test can prove the new revision is the - # one actually being served. - - uses: actions/upload-artifact@v7 - if: github.event_name != 'pull_request' - with: - name: index-html - path: dist/index.html - retention-days: 1 - - deploy: - name: Deploy to Railway - # Never from a pull request, including forks — secrets are not exposed there anyway. - if: github.event_name != 'pull_request' - needs: build - runs-on: ubuntu-latest - environment: - name: production - url: https://turingdb-docs-production.up.railway.app - env: - RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} - SITE_URL: https://turingdb-docs-production.up.railway.app - steps: - - uses: actions/checkout@v7 - - # `secrets` is not available in an `if:` expression, so this is checked in the shell. - - name: Require the deploy token - run: | - if [ -z "$RAILWAY_TOKEN" ]; then - echo "::error::RAILWAY_TOKEN is not set. Create a project token in the Railway" - echo "::error::dashboard (project -> Settings -> Tokens, environment: production)" - echo "::error::and add it as a repository secret named RAILWAY_TOKEN." - exit 1 - fi - - - name: Install the Railway CLI - run: npm i -g @railway/cli@5 --foreground-scripts - - # railway up uploads this directory and builds the Dockerfile remotely. The - # Dockerfile's own page-count guard means a broken prerender fails the build rather - # than shipping an empty site. - - name: Deploy - run: railway up --ci --service turingdb-docs - - - uses: actions/download-artifact@v8 - with: - name: index-html - path: expected - - - name: Wait for the new revision to serve - run: | - want=$(md5sum < expected/index.html | cut -d' ' -f1) - echo "expecting index.html md5 $want" - for i in $(seq 1 20); do - got=$(curl -fsS --max-time 20 "$SITE_URL/?cb=$GITHUB_SHA-$i" 2>/dev/null | md5sum | cut -d' ' -f1) - if [ "$got" = "$want" ]; then - echo "new revision is live" - exit 0 - fi - echo " attempt $i: serving $got" - sleep 15 - done - echo "::error::live index.html still does not match this build after ~5 minutes" - exit 1 - - - name: Smoke-test routes - run: | - fail=0 - for p in / /quickstart /concepts/overview /pythonsdk/reference; do - code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 20 "$SITE_URL$p") - echo " $p -> $code" - [ "$code" = "200" ] || fail=1 - done - code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 20 "$SITE_URL/does-not-exist") - echo " /does-not-exist -> $code (want 404)" - [ "$code" = "404" ] || fail=1 - exit $fail diff --git a/README.md b/README.md index 0fe5aa2..0d708d9 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,9 @@ Images go in `public/images/` and are referenced from `.mdx` as `/images/…`. - **Netlify / Cloudflare Pages** — the generated `dist/_redirects` is already correct - **S3 + CloudFront** — index document `index.html`, error document `404.html`, plus a rule mapping `/path` to `/path/index.html` -- **Railway** — pushes to `main` deploy automatically via - `.github/workflows/deploy.yml`; `railway up` from this directory does it by hand. - `railway.json` selects the Dockerfile. +- **Railway** — pushes to `main` deploy automatically through Railway's GitHub + integration; `railway up` from this directory does it by hand. `railway.json` selects the + Dockerfile. Assets under `/assets/` and `/fonts/` are content-hashed or never edited in place and are served `immutable`; HTML revalidates. @@ -121,17 +121,12 @@ smears every stem. ## CI -`.github/workflows/deploy.yml`: +`.github/workflows/build.yml` runs on pull requests to `main` and on pushes to `main`: +`npm run gen`, `tsc --noEmit`, the anchor test, `npm run build`, and an assertion that at +least 35 pages prerendered. -- **pull requests to `main`** — build check only: `npm run gen`, `tsc --noEmit`, - the anchor test, `npm run build`, and an assertion that all 35 pages prerendered. -- **pushes to `main`** — the same check, then `railway up`, then it waits until the live - `index.html` byte-matches this commit's build and smoke-tests four routes plus a 404. - Deploys never run from a pull request. - -Requires one repository secret, **`RAILWAY_TOKEN`** — a Railway *project* token for the -`production` environment (Railway dashboard → project → Settings → Tokens). Without it the -deploy job fails immediately with a message saying so, rather than half-deploying. +Deploying is not part of CI. Railway's GitHub integration builds the Dockerfile and deploys +every push to `main`. ## Verifying the rendering