Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: ci

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
ci:
name: install / lint / typecheck / build / smoke
runs-on: ubuntu-latest
timeout-minutes: 5
env:
CI: "true"
NEXT_TELEMETRY_DISABLED: "1"
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Typecheck
run: npm run typecheck

- name: Build
run: npm run build

- name: Smoke (landing page renders from built server)
run: npm run smoke
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# hello

[![ci](https://github.com/lmanualm/hello/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/lmanualm/hello/actions/workflows/ci.yml)

`hello` is a small, real, public product. v0.1 ships a one-page landing site
that introduces what we're building and captures interested visitors. Once that
loop works end-to-end (code → CI → deploy → verify), we'll grow scope.
Expand Down Expand Up @@ -36,6 +38,7 @@ Then open <http://localhost:3000>.
| `npm run start` | Serve the production build. |
| `npm run lint` | Run Next.js linting. |
| `npm run typecheck`| Run `tsc --noEmit`. |
| `npm run smoke` | Build-server smoke: boots `next start` on a free port and asserts the landing page renders. |

## Layout

Expand All @@ -46,6 +49,14 @@ app/
globals.css # base styles
```

## CI

Every push to `main` and every pull request runs
[`.github/workflows/ci.yml`](.github/workflows/ci.yml): `npm ci`, lint,
typecheck, build, and a smoke test that boots the built server and
asserts the landing page renders. A failing step blocks the merge. See
[`docs/decisions/ci.md`](./docs/decisions/ci.md) for the rationale.

## License

MIT — see [`LICENSE`](./LICENSE).
106 changes: 106 additions & 0 deletions docs/decisions/ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# CI decision

**Status:** accepted (v0.1)
**Issue:** HEL-4
**Date:** 2026-05-20

## Decision

CI runs as a single GitHub Actions workflow (`.github/workflows/ci.yml`)
on every push to `main` and every pull request targeting `main`. The job
runs five steps in sequence:

1. `npm ci` — install from the lockfile (with the built-in `setup-node`
npm cache).
2. `npm run lint` — `next lint` against `next/core-web-vitals`.
3. `npm run typecheck` — `tsc --noEmit`.
4. `npm run build` — production Next.js build.
5. `npm run smoke` — boots `next start` on an OS-assigned free port,
fetches `/`, and asserts the landing-page placeholder markers
(`<h1>hello</h1>` and `Something small is on the way.`) are present
in the rendered HTML.

A failure in any step fails the workflow and blocks the PR from being
merged (the workflow's `name`/job appears as a required status check
once branch protection is configured on `main`).

## Why GitHub Actions

- The repo will be hosted on GitHub (HEL-3 stack-decision); using
Actions keeps CI in the same place as the code and PRs.
- Free minutes are plenty for a single-job workflow this small.
- `actions/setup-node` has first-class npm caching, which keeps the
wall-clock under the ~2 min target on HEL-4 without any custom
cache config.
- No third-party CI provider to pay for, configure, or replace if we
swap hosts later.

## Why these five steps (and only these five)

- **Lint** catches the cheap mistakes that ESLint can find in seconds.
- **Typecheck** catches the cheap mistakes that the TypeScript compiler
can find. Both lint and typecheck are read-only and parallel-safe; we
keep them as separate steps so a failure log points at the right
thing.
- **Build** is the closest local proxy for "would this actually deploy
cleanly?" — it exercises the same code path Vercel/Netlify/Render
will run when we wire up deploys.
- **Smoke** is the only step that actually starts the built server and
asserts a user-visible behaviour (the landing page renders). One
end-to-end check beats a directory of micro-tests at this stage of
the product.
- We deliberately do **not** run unit tests in CI yet — there are no
unit tests in the repo. We will add a `test` step alongside lint /
typecheck when there is something worth testing in isolation.

## Why the smoke test boots a server

A static-output assertion (e.g. grepping `.next/server/app/page.html`)
would be cheaper but would also drift from reality the first time we
add a dynamic page or a Route Handler. Booting `next start` and
fetching `/` exercises the same runtime that production will use, so
the smoke catches regressions in routing, layout, and metadata, not
just markup.

The smoke script asks the kernel for a free port instead of binding to
3000 / 3100, so it survives the local dev environment without colliding
with other services (the Paperclip control plane already binds 3100 on
the engineering box).

## Wall-clock budget

The HEL-4 acceptance asks for CI under ~2 minutes. With the npm cache
warm, on `ubuntu-latest`, expected timings are roughly:

| Step | Cold cache | Warm cache |
| ---------- | ---------- | ---------- |
| install | ~30 s | ~10 s |
| lint | ~5 s | ~5 s |
| typecheck | ~10 s | ~10 s |
| build | ~25 s | ~25 s |
| smoke | ~10 s | ~10 s |
| **total** | ~80 s | ~60 s |

The job has a 5-minute timeout-minutes guard so a runaway step fails
loud instead of burning Actions minutes silently.

## Out of scope

- Deploys (lives in HEL-6).
- Coverage / unit-test reporting (no tests exist yet).
- Caching of the Next.js `.next/cache` between runs (the build is fast
enough that the cache complexity isn't worth it yet).
- Branch protection rules — those are configured on GitHub, not in this
repo, and require a CEO-owned repo admin action once the repo is
pushed to GitHub.

## Demonstrating that CI gates merges

The HEL-4 acceptance asks for "a failing example PR demonstrates CI
blocks the merge". The hello repo has no GitHub remote yet (it lives
on the engineering box only), so we cannot open a real PR. As a local
substitute, the issue update records the output of running each CI
step (lint, typecheck, build, smoke) against the green tree and then
against a deliberately broken tree. Once the repo is pushed to GitHub
and a remote exists, the first failing PR — opened, observed-red,
closed — will satisfy this acceptance criterion directly.
Loading
Loading