The three gates
Typecheck, lint, build — three commands, three exit codes, and the zsh trap that hides a failure.
npm run typecheck
npm run lint
npm run buildThree commands, not one. next build in Next 16 does not run ESLint, so a green build
is not a green lint, and collapsing them hides real failures.
Each app carries all three:
| Storefront | Docs | |
|---|---|---|
| typecheck | npm run typecheck | npm run typecheck -w @glemor/docs |
| lint | npm run lint | npm run lint -w @glemor/docs |
| build | npm run build | npm run docs:build |
Capture the exit code without a pipe
This shell is zsh. PIPESTATUS is empty there, so cmd | tail followed by a
${PIPESTATUS[0]} check reads as success no matter what happened. Redirect to a log and
check $? directly:
npm run typecheck > /tmp/tc.log 2>&1; echo "EXIT=$?"; tail -30 /tmp/tc.logNever silence the error
No eslint-disable, no @ts-ignore, no loosening tsconfig to make a gate pass. Fix the
cause. A suppressed error is a bug with the alarm switched off.
What the gates do not catch
CI runs all three on every push to main, plus node scripts/release.mjs --self-test.
Nothing in that pipeline checks the following, so check them yourself:
- Internal links resolve. Nothing verifies that
<Link href="/x">has a route behind it. A dead internal link passes all three gates and 404s in production. This site is mostly links; it is the first thing to re-check after moving a page. - Reduced motion. Every storefront animation routes through the shared
useReducedMotionguard. Animatetransformandopacityonly. - Money is integer sen. Never a float, never parsed from client input. Prices are looked up server-side from the catalogue, always.
- Content honesty. No invented founder, date, award, press quote, testimonial,
stockist address or opening hours. Verified facts live in
apps/storefront/src/lib/site.ts. Scent notes are verified for Pear Silk only.
The full list, and the commit convention the release depends on, is in
CONTRIBUTING.md.
Interactive scaffolders
create-* CLIs prompt even when you pass flags, and piping </dev/null makes them exit
0 having created nothing — a silent no-op that looks exactly like success. If you must
use one, pipe real answers (yes N | npx ...) and then verify the files exist on disk
before believing it.