GlemorDocs
Engineering

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 build

Three 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:

StorefrontDocs
typechecknpm run typechecknpm run typecheck -w @glemor/docs
lintnpm run lintnpm run lint -w @glemor/docs
buildnpm run buildnpm 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.log

Never 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 useReducedMotion guard. Animate transform and opacity only.
  • 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.

On this page