Skip to content

ts-archunit ​

Architecture testing for TypeScript. Encode structural rules as executable checks that run in CI and catch violations on the PR that introduces them — not 18 months later in a manual audit.

Inspired by Java's ArchUnit. Powered by ts-morph.

bash
npm install -D @nielspeter/ts-archunit
npx ts-archunit init        # scaffold config + rules, then `npm run arch`

Get Started → · What Can It Check? · GitHub


Rules read like English ​

If you can read this sentence, you can write architecture rules:

typescript
classes(p).that().extend('BaseRepository').should().notContain(call('parseInt'))
  • classes(p).that() — select which classes
  • .extend('BaseRepository') — filter to subclasses
  • .should().notContain(call('parseInt')) — assert what must be true

Spread rules into arch.rules.ts and run npm run arch, or run them inside vitest/jest — same rules, your choice of runner.

What other tools can't do ​

Every existing tool checks which files import which. That's useful, but it misses what happens inside your functions — the real source of architecture rot:

typescript
// "Repositories must use the shared helper, not inline parseInt"
classes(p).that().extend('BaseRepository').should().notContain(call('parseInt'))

// "Query options must use typed unions, not bare string"
types(p).that().haveProperty('orderBy').should().havePropertyType('orderBy', not(isString()))

// "No circular dependencies between feature modules"
slices(p).matching('src/features/*/').should().beFreeOfCycles()
Capabilityts-archunitdependency-cruisereslint-plugin-boundaries
Import path rules✅✅✅
Body analysis (what's called inside functions)✅❌❌
JSX element rules (design system, a11y)✅❌❌
Type checking (string vs typed union)✅❌❌
Cycle detection✅✅❌
Smell detection (duplicates, inconsistencies)✅❌❌
Config enforcement (strict: true can't drift)✅❌❌
Baseline (gradual adoption)✅❌❌
GitHub PR annotations✅❌❌

ts-archunit complements your linter — it doesn't replace it. For the honest breakdown of where it overlaps eslint, Biome, and dependency-cruiser (and where it doesn't), see How It Fits.

A violation tells you why and how to fix it ​

Architecture Violation [1 of 1]

  Rule: Classes extending 'BaseRepository' should not contain call to 'parseInt'

  src/repositories/webhook.repository.ts:7 — WebhookRepository

  Why: BaseRepository provides extractCount() — inline parseInt diverges
  Fix: Replace parseInt(x, 10) with this.extractCount(result)

    > 7 |     const total = parseInt(countResult.count, 10)

In GitHub Actions this appears inline on the PR diff, right where the violation was introduced.

What it can enforce ​

A quick sample — see the full gallery in What Can It Check?:

  • Layers & dependencies — domain must not import infrastructure; no cycles between features
  • Code patterns — repositories use the shared helper, not inline parseInt; services throw typed errors
  • Type safety — no any properties, no as assertions; orderBy is a typed union, not bare string
  • JSX & design system — no raw <button>; every <img> has alt; no dangerouslySetInnerHTML
  • Config drift — strict: true stays on (tsconfig())
  • Code smells — copy-pasted logic and inconsistent siblings, caught by AST similarity

Adopt gradually ​

You don't need to fix everything to start. Baseline mode records existing violations and only fails on new ones — so a large legacy codebase can adopt rules incrementally and ratchet down over time. See Setup & Best Practices.

AI agents need guardrails ​

AI-generated code doesn't know your team's conventions, and every agent PR looks correct in isolation. ts-archunit is the guardrail: rules run in CI, and violations carry the exact context — what's wrong, why, how to fix — an agent needs to self-correct. See AI Agents.


Ready to start? ​

Get Started → — install, scaffold, and run your first rule in five minutes.

Released under the MIT License.