Litestore · Developer guide
Getting started

Commands

This page tells you what each script in package.json does and which one to run when.

Every command on this page is a script in package.json. You run each one with bun run <name> from the repository root.

packageManager pins bun@1.2.2, and .github/workflows/verify.yml installs that same version. CI and your machine therefore run the same tool at the same version.

Daily

bun run dev

This runs next dev -p 3000 --turbopack. The storefront opens on :3000 and the admin at /admin.

bun run build

This runs next build with NODE_OPTIONS=--max-old-space-size=6144. The prebuild hook runs icons first.

bun run start

This runs next start -p 3000.

bun run icons

This runs scripts/build-icons.ts, which writes the icon sprite and types/icons.d.ts.

Git ignores both files. On a fresh clone, you need to run this before typecheck or the sprite contract test can pass.

bun run email

This runs SKIP_ENV_VALIDATION=1 email dev, which starts the React Email preview server for the templates in emails/.

bun run format

This runs biome format --write ..

bun run clean:next

This deletes the .next-build and .next-dev directories.

bun run scaffold:analytics-card

This runs scripts/scaffold-analytics-card.ts <kebab-key> "<Human Title>". It prints snippets you can paste into each place an analytics card has to be wired up. Those places are the query, service, action, row builder, table registry, dashboard card and module registry.

Database

bun run db:generate

This runs prisma generate. The postinstall hook already runs it for you after bun install.

bun run db:migrate

This runs prisma migrate dev.

bun run db:push

This also runs prisma migrate dev. It is the same command as db:migrate, not prisma db push.

bun run db:deploy

This runs prisma migrate deploy. It applies the migrations already committed and does not generate new ones.

bun run db:reset

This runs prisma migrate reset. It drops the database and re-applies every migration. Then it runs the seed through the prisma.seed hook (bun prisma/seed.ts).

bun run db:seed

This runs tsx prisma/seed.ts.

bun run db:studio

This runs prisma studio --port 5556.

bun run db:guarantees:audit

This runs scripts/check-db-guarantees.ts. It reads the database without changing it and checks the constraints, indexes, triggers and views that the migrations install. It fails when a drift view or a pre-index duplicate probe returns rows.

bun run db:guarantees:audit:strict

This runs the same audit with --strict. In strict mode it also fails on any CHECK constraint that is not validated.

db:push is not a schema push

db:push and db:migrate both run prisma migrate dev, so either one writes a migration file. The committed history in prisma/migrations/ starts with 20260726143000_baseline_with_db_guarantees, which contains the schema plus every CHECK constraint and case-insensitive unique index. After it comes 20260730090000_performance_indexes.

db:seed has a similar trap. tsx is not a dependency in package.json, so bun run db:seed fails unless you have tsx installed globally. Run the file directly instead:

bun prisma/seed.ts

This is the same file. The prisma.seed hook uses it, and db:reset calls it.

Checks

bun run lint

This is the short check you run while working. It runs these steps in order:

  1. biome check --write .
  2. lint:cache
  3. lint:discipline
  4. lint:dead
  5. lint:flex
  6. lint:critical-tests
  7. lint:no-as-any
  8. lint:cleanliness

lint writes fixes to your files. It is the only command on this page that changes your working tree without you asking for it.

bun run lint:discipline

This runs scripts/check-discipline.ts. It checks that code follows the project's patterns for icons, the table factory, CRUD routing and similar rules.

bun run lint:critical-tests

This runs scripts/check-critical-tests.ts. It fails when a file that handles money or other critical logic has no test.

bun run lint:cleanliness

This runs thirteen checks one after another:

  1. orphan exports
  2. dead server actions
  3. dead Prisma fields
  4. field balance
  5. duplicate utils
  6. unused deps
  7. hacks
  8. events
  9. cache tags
  10. guarantees contract
  11. client/server boundary
  12. client bundle
  13. discipline hygiene
bun run lint:knip

This runs knip, which finds files and exports that nothing references. It is not part of verify.

bun run test

This runs vitest in watch mode.

bun run vitest run

This runs the unit and contract tests once. Here vitest is the binary in node_modules/.bin, not a script in package.json.

bun run test:integration

This runs vitest run --config vitest.integration.config.ts. It runs the files under test/integration/ one at a time against a real litestore_test Postgres database. You can point it at a different database with TEST_DATABASE_URL.

bun run typecheck

This runs tsc --noEmit with NODE_OPTIONS=--max-old-space-size=12288.

bun run typecheck:strict

This runs the same check with --strict --noImplicitAny --noImplicitReturns --noImplicitThis --noUnusedLocals --noUnusedParameters.

Typecheck memory

typecheck asks Node for a 12 GB heap. On a smaller machine, stop the dev server and run it on its own.

The verify gate

bun run verify

verify decides whether a change can be merged. .github/workflows/verify.yml runs it on every push to main and on every pull request.

In package.json, verify is a single chain of commands joined with &&. They run one after another, and the chain stops at the first failure. These are the steps:

  1. biome check .
  2. lint:discipline
  3. lint:cache
  4. lint:dead
  5. lint:flex
  6. lint:theme-boundaries
  7. lint:no-as-any
  8. lint:critical-tests
  9. lint:event-completeness
  10. lint:nested-soft-delete
  11. lint:public-feed-visibility
  12. lint:nested-hidden
  13. lint:cleanliness
  14. vitest run
  15. typecheck

Unlike in lint, Biome runs here without --write. Between Biome and the typechecker, the gate runs twelve lint checks and the test suite.

Each step is its own script. When one fails, you can run just that step instead of the whole chain.

How the 25 checks map onto the gate

Eleven of the steps each run exactly one scripts/check-*.ts file:

lint:discipline
lint:cache
lint:dead
lint:flex
lint:theme-boundaries
lint:no-as-any
lint:critical-tests
lint:event-completeness
lint:nested-soft-delete
lint:public-feed-visibility
lint:nested-hidden

lint:cleanliness runs thirteen more:

lint:orphans
lint:dead-actions
lint:dead-schema
lint:field-balance
lint:dupes
lint:deps
lint:hacks
lint:events
lint:cache-tags
lint:guarantees
lint:client-boundary
lint:client-bundle
lint:hygiene

Together they cover 24 of the 25 scripts/check-*.ts files.

The last one, check-db-guarantees.ts, is left out of the gate because it needs a live database. You run it with db:guarantees:audit.

Running the gate in parallel

bun run verify:parallel

This runs scripts/verify.ts, which starts the same fifteen steps at the same time. It runs at most cpus − 1 of them at once, and you can change that limit with VERIFY_CONCURRENCY.

It reports whether each step passed and how long it took. It shows every failure instead of stopping at the first one. That makes it the better choice when several things are broken at once.

The same gate locally

Lefthook runs the same checks on your machine. bun install sets up the hooks through the prepare script.

  1. pre-commit runs lint:discipline, lint:critical-tests and vitest run.
  2. pre-push runs verify.
  3. commit-msg rejects messages shorter than 10 characters or 2 words. It lets through messages that start with Merge, Revert, fixup! or squash!.

When Prisma disagrees with the schema

Sometimes TypeScript says a model does not exist even though you can see it in schema.prisma. That means the generated client is out of date. Regenerate it:

bun run db:generate

This usually happens after you pull a branch that changed the schema.

Which command to run

While you work, run lint. It fixes what it can, and it runs fewer checks.

Before you push, run verify, because CI and the pre-push hook both run it. If you already know several steps are failing, run verify:parallel to get the whole list in one pass.

Neither gate checks the database, because that needs a live connection.

If you changed a migration, a passing verify does not prove the guarantees still hold. Run db:guarantees:audit against the database.

Next Steps

On this page