Opinionated boundary conventions for JavaScript and TypeScript workspaces.
The executable is boundaries.
pnpm add -D @howells/boundariesTry without installing:
npx @howells/boundaries
npx @howells/boundaries init --dry-run
npx @howells/boundaries check --profile feature-sliced
npx @howells/boundaries --helpInitialize boundary config:
pnpm exec boundaries initThis adds:
- root
turbo.jsonboundary rules - package-level
turbo.jsontags - a root
package.jsonscript:"boundaries": "boundaries check"
Run checks:
pnpm boundariesor:
pnpm exec boundaries checkCheck a common JavaScript source architecture without requiring Turbo:
pnpm exec boundaries check --profile feature-sliced
pnpm exec boundaries check --profile next-feature
pnpm exec boundaries check --profile clean-nodeExplain a relationship:
pnpm exec boundaries explain apps/web packages/ui
pnpm exec boundaries explain apps/web apps/adminMachine-readable output:
pnpm exec boundaries --schema
pnpm exec boundaries init --dry-run --json
pnpm exec boundaries check --profile feature-sliced --json
pnpm exec boundaries explain apps/web packages/ui --jsonThe default tags are:
type:app
type:package
type:tooling
scope:<workspace-name>
visibility:public
visibility:internal
The default rules are:
type:app cannot depend on type:app
type:package cannot depend on type:app
type:tooling cannot depend on type:app
This blocks app-to-app imports and keeps shared packages from reaching into deployable apps.
visibility:* tags are generated as metadata, but no default rule is attached to them yet. Public packages often depend on private dev tooling packages, so runtime visibility policy needs a more precise model.
boundaries check validates the generated convention layer, then delegates to:
turbo boundariesUse this in Turborepo repos that already have turbo installed.
boundaries check --profile <name> uses the built-in JavaScript import scanner instead of Turbo. It supports relative imports plus common root aliases such as @/, ~/, and src/.
Profiles enforce layer direction over src/ imports:
feature-sliced: app -> pages -> widgets -> features -> entities -> shared
next-feature: app/pages -> components -> features -> entities -> lib/shared
clean-node: adapters/infrastructure -> application -> domain
Higher layers may import lower layers. Lower layers may not import higher layers.