Skip to content
AstroCraft Docs
On this theme

Commands & Testing

8-BitQuest has nine pnpm scripts and no hidden tooling. The interesting one is test, which runs a discovery-based runner with no framework at all; the rest are the standard Astro lifecycle plus the lint/format pair.

The scripts

"dev":     "astro dev",
"build":   "astro build",
"preview": "astro preview",
"start":   "node ./dist/server/entry.mjs",
"astro":   "astro",
"check":   "astro check",
"lint":    "eslint .",
"format":  "eslint . --fix && prettier -w \"**/*\" --ignore-unknown --cache",
"test":    "node ./scripts/test.mjs"
  • pnpm dev — the dev server at localhost:4321.
  • pnpm build — the production build. Because the contact route is server-rendered, the output splits into dist/client/ (prerendered pages) and dist/server/entry.mjs (the one server route).
  • pnpm preview — serves the built output for a quick look.
  • pnpm start — runs the built Node server, honouring HOST/PORT. This is what you run in production. See Deployment.
  • pnpm checkastro check, a full type-check across .astro and .ts.
  • pnpm lint — ESLint over the whole project.
  • pnpm formateslint --fix and then Prettier, with a cache.
  • pnpm test — the check runner, below.

The verify chain

The one command to run before you commit, and the exact chain CI runs on every push and pull request:

pnpm lint && pnpm check && pnpm build && pnpm test

The build is the real check — content-schema errors, config typos and broken references all surface there rather than in the browser. CI (.github/workflows/ci.yml) runs this same chain on the placeholder SITE_URL, on the Node version that matches the engines floor, so a green CI run means the code builds; it does not mean your production domain is set.

The test runner

scripts/test.mjs is the whole of pnpm test, and it is deliberately tiny — no Jest, no Vitest, no config, nothing to register:

  • It discovers every *.test.ts file under src/ and runs each one under node --experimental-strip-types. The explicit flag is what lets it work on the Node 22.13 floor, rather than only on 23.6+ where type-stripping is on by default.
  • Zero checks found is a failure, not a pass. The point of discovery is that it cannot quietly stop finding the checks it is meant to run — a boilerplate that ships a green-but-empty test command teaches the opposite of the rule it enforces.

There are eleven checks today: the contact validation and spam gates (contact.test.ts), the Resend send boundary (resend.test.ts), the JSON-LD builders (schema.test.ts), the RSS renderer (rss.test.ts), reading time (readingTime.test.ts), the nav active-link logic (nav.test.ts), the post and project card mappings (postCards.test.ts, projectCards.test.ts), the social-URL resolver (social.test.ts), the listbox kernel (_listbox.test.ts) and the password strength scorer (strength.test.ts).

Why the tested helpers are pure

The checks run under Node’s type-stripping, which erases type-only imports but cannot resolve Astro’s virtual modules (astro:content) as runtime values. That is why the theme keeps its logic pure: the card mappings (postCards.ts, projectCards.ts) import types only and are tested, while the data-access siblings that read astro:content at runtime (blogData.ts, projectData.ts) are intentionally untested — the split is what keeps the whole suite runnable without a bundler. When you add logic worth testing, keep it in a pure module with type-only imports and drop a *.test.ts beside it.

The tooling rules

A few conventions are worth knowing before they surprise you:

  • Class ordering is Prettier’s job. prettier-plugin-tailwindcss sorts utility classes; do not hand-order them. Run pnpm format and let it sort.
  • Import ordering is ESLint’s job. simple-import-sort orders imports (side-effect first, then alphabetised), so run pnpm format rather than sorting by hand.
  • The one lint waiver. The primitive contract exports a tv() config from .astro frontmatter, which collides with astro/no-exports-from-components. A scoped override disables just that rule for src/components/ui/**/*.astro and src/components/svg/**/*.astro, documented in eslint.config.mjs. Everything else in those files lints normally.
  • scripts/ is linted like shipped code. There is no blanket ignore; the one script left lints clean and is held to the same bar.
NEXT STEPTroubleshooting