layerguard
v0.1.1
Published
Framework-agnostic CLI tool that enforces architectural layer boundaries in TypeScript/JavaScript projects
Maintainers
Readme
Layerguard
Architectural layer enforcement for TypeScript and JavaScript projects.
Layerguard prevents architectural violations before they happen. Define your layers, declare how dependencies can flow between them, and Layerguard enforces those rules in CI, pre-commit hooks, or your editor.
Why Layerguard?
- Catch violations early - Get instant feedback when code violates your architecture
- Framework-agnostic - Works with Next.js, Vite, Angular, Node backends, and more
- Fast incremental checking - Only re-checks files that changed
- Zero runtime impact - Static analysis only, no runtime dependencies
- Monorepo ready - First-class workspace support for pnpm, npm, and Yarn workspaces
- ESLint integration - Inline editor feedback alongside your other linting rules
Table of Contents
- Installation
- Quick Start
- Configuration
- CLI Commands
- Monorepo Support
- Framework Support
- ESLint Integration
- GitHub Actions
- VS Code Extension
- Programmatic API
- License
Installation
npm install --save-dev layerguardOr with your preferred package manager:
pnpm add -D layerguard
yarn add -D layerguardRequirements: Node.js 18.0.0 or higher, TypeScript 4.7.0 or higher
Quick Start
Run the interactive setup wizard:
npx layerguard initThe wizard detects your project structure, suggests layers, and generates layerguard.config.ts.
Then validate your architecture:
npx layerguard checkIf code violates the rules:
layerguard check
✗ 1 violation
ERROR Layer violation: repository -> services
src/repository/users.ts:3 -> src/services/auth.ts
Repository cannot import from services.Configuration
Create layerguard.config.ts in your project root:
import { defineConfig } from 'layerguard'
export default defineConfig({
layers: {
handlers: { path: 'src/handlers/' },
services: { path: 'src/services/' },
repository: { path: 'src/repository/' },
},
flow: [
'handlers -> services',
'services -> repository',
],
})Layers
Each layer represents an architectural tier in your project:
layers: {
// Layer name -> configuration
pages: { path: 'src/pages/' },
components: { path: 'src/components/' },
hooks: { path: 'src/hooks/' },
utils: { path: 'src/utils/' },
}Files within a layer directory belong to that layer. Files outside all layers are "unlayered" and can be handled via the unlayeredImports rule.
Flow Rules
Flow rules define allowed dependency directions:
flow: [
'pages -> components', // pages can import from components
'pages -> hooks', // pages can import from hooks
'components -> hooks', // components can import from hooks
'hooks <-> utils', // bidirectional: hooks and utils can import each other
]A -> B- Unidirectional: A can import from B, but B cannot import from AA <-> B- Bidirectional: A and B can import from each other
Any import not explicitly allowed is a violation.
Sublayers and Isolation
Layers can contain sublayers with their own rules:
layers: {
components: {
path: 'src/components/',
sublayers: {
features: { path: 'src/components/features/', isolated: true },
shared: { path: 'src/components/shared/' },
},
flow: ['features -> shared'],
},
}With isolated: true, sibling directories within the sublayer cannot import from each other. For example, features/auth/ cannot import from features/billing/.
Public API Enforcement
Restrict which files can be imported from outside a layer:
layers: {
services: {
path: 'src/services/',
publicApi: 'index.ts', // Only index.ts can be imported from outside
},
}Other files within the layer become private. This encourages clean module boundaries.
You can specify multiple public files:
publicApi: ['index.ts', 'types.ts']Rules
Configure additional architectural checks:
rules: {
// Circular dependency detection
circular: 'error', // 'error' | 'warn' | 'off' (default: 'error')
// Orphan file detection (files not imported anywhere)
orphans: 'warn', // 'error' | 'warn' | 'off' (default: 'off')
// Whether to enforce rules on type-only imports
typeOnlyImports: 'ignore', // 'enforce' | 'ignore' (default: 'ignore')
// Imports from layered files to unlayered files
unlayeredImports: 'warn', // 'error' | 'warn' | 'ignore' (default: 'ignore')
// How to resolve barrel re-exports
barrelResolution: 'import-site', // 'import-site' | 'origin' (default: 'import-site')
// Workspace package import handling
workspaceImports: 'ignore', // 'enforce' | 'ignore' (default: 'ignore')
// Maximum import chain depth (transitive imports)
maxImportDepth: 5, // Warns if exceeded
// Maximum imports per file
maxImportsPerFile: 20, // Warns if exceeded
}Barrel Resolution Modes:
import-site- Check against where the import points (e.g.,services/index.ts)origin- Trace re-exports to their source file (catches hidden violations through barrels)
Exceptions
Document exceptions when violations are intentional:
exceptions: [
{
from: 'src/repository/users.ts',
to: 'src/services/auth.ts',
reason: 'User repository needs auth service for password hashing',
},
{
from: 'src/legacy/**/*.ts',
to: 'src/**/*.ts',
reason: 'Legacy code is being migrated gradually',
},
]Exceptions require a reason - this serves as living documentation for architectural decisions.
Ignore Patterns
Exclude files from analysis:
ignore: [
'**/*.test.ts',
'**/*.spec.ts',
'**/mocks/**',
'**/fixtures/**',
]TypeScript Configuration
Specify custom tsconfig path(s):
// Single tsconfig
tsconfig: 'tsconfig.app.json',
// Multiple tsconfigs (merged)
tsconfig: ['tsconfig.app.json', 'tsconfig.server.json'],CLI Commands
check
Validate architecture rules:
layerguard check [options]| Option | Description |
|--------|-------------|
| --ci | Output in GitHub Actions annotation format |
| --json | Output violations as JSON |
| --no-color | Disable colored output |
| --type-only | Enforce rules on type-only imports |
| --watch, -w | Watch mode: re-check on file changes |
| --no-cache | Disable incremental caching |
| --package, -p | Check a specific workspace package |
| --all | Check all workspace packages |
| --github-pr-comment | Post results as a PR comment |
| --pr-number | PR number for comment (auto-detected in CI) |
Examples:
layerguard check # Standard check
layerguard check --ci # GitHub Actions annotations
layerguard check --json # JSON output for tooling
layerguard check --watch # Watch mode
layerguard check --package apps/web # Check specific package
layerguard check --all # Check all packages in monoreposhow
Display architecture diagram:
layerguard show [options]| Option | Description |
|--------|-------------|
| --ascii | Use ASCII characters instead of Unicode |
| --flow-only | Show only flow rules, no diagram |
init
Interactive setup wizard:
layerguard init [options]| Option | Description |
|--------|-------------|
| -y, --yes | Skip prompts and use detected defaults |
report
Generate HTML or Markdown report:
layerguard report [options]| Option | Description |
|--------|-------------|
| --output, -o | Output file path (default: layerguard-report.html) |
| --markdown, --md | Output as Markdown instead of HTML |
| --stdout | Print to stdout instead of file |
| --from | Load historical data from JSON for trend charts |
| --title | Custom report title |
Examples:
layerguard report # Generate HTML report
layerguard report --markdown # Markdown summary
layerguard report -o reports/arch.html # Custom output path
layerguard report --from history.json # Include trend dataMonorepo Support
Layerguard detects pnpm, npm, and Yarn workspaces automatically.
Per-package Configuration
Each package can have its own layerguard.config.ts:
my-monorepo/
├── package.json # workspaces: ["packages/*", "apps/*"]
├── packages/
│ └── shared/
│ └── layerguard.config.ts
└── apps/
└── web/
└── layerguard.config.tsCheck Specific Package
# By name
layerguard check --package @myorg/web
# By path
layerguard check --package apps/webCheck All Packages
layerguard check --allThis finds all packages with layerguard.config.ts and checks them in sequence.
Cross-package Imports
By default, workspace package imports (e.g., import { foo } from '@myorg/shared') are treated like external packages. Enable cross-package enforcement with:
rules: {
workspaceImports: 'enforce',
}Framework Support
Set framework to enable framework-specific intelligence:
export default defineConfig({
framework: 'nextjs-app',
// ...
})| Value | Framework |
|-------|-----------|
| nextjs-app | Next.js App Router (page.tsx, layout.tsx, route.ts) |
| nextjs-pages | Next.js Pages Router (_app.tsx, _document.tsx) |
| vite-react | Vite + React (main.tsx, vite.config.ts) |
| vite-react-router | Vite + React Router file conventions |
| vite-tanstack-router | Vite + TanStack Router file conventions |
| vue-nuxt | Nuxt directory structure |
| angular | Angular module conventions |
| node-backend | Node.js backend patterns |
| custom | Manual configuration only |
Framework detection enables:
- Entry point recognition (excluded from orphan detection)
- Framework-specific file patterns
- Better layer suggestions in
layerguard init
ESLint Integration
Get inline editor feedback with the ESLint plugin:
// eslint.config.js
import layerguard from 'layerguard/eslint'
export default [
...layerguard.configs.recommended,
// your other config...
]The plugin reads your layerguard.config.ts and reports violations as you type.
GitHub Actions
Add to your CI workflow:
name: Architecture Check
on: [push, pull_request]
jobs:
layerguard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx layerguard check --ciThe --ci flag outputs GitHub Actions annotations, showing violations inline in the PR diff.
PR Comments
Post a summary comment on pull requests:
- run: npx layerguard check --github-pr-comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}This requires the gh CLI (pre-installed on GitHub-hosted runners).
VS Code Extension
Install the Layerguard extension for:
- Inline diagnostics showing violations as you type
- Quick fixes for common issues
- Hover information showing layer relationships
Programmatic API
Use Layerguard programmatically in your tools:
import { loadConfig, validateConfig } from 'layerguard/config'
import { buildDependencyGraph } from 'layerguard/parser'
import { createFlowChecker } from 'layerguard/enforcer'
// Load and validate config
const { config } = await loadConfig(process.cwd())
const validation = validateConfig(config, process.cwd())
if (!validation.valid) {
console.error(validation.errors)
process.exit(1)
}
// Build dependency graph
const graph = buildDependencyGraph({
projectRoot: process.cwd(),
include: ['src/**/*.ts'],
})
// Check for violations
const checker = createFlowChecker(config)
const result = checker.checkGraph(graph)
console.log(`Found ${result.violations.length} violations`)Exports
| Export | Description |
|--------|-------------|
| layerguard | Main entry point |
| layerguard/config | Config loading and validation |
| layerguard/parser | Dependency graph building |
| layerguard/enforcer | Violation checking |
| layerguard/plugins | Framework plugin interfaces |
| layerguard/eslint | ESLint plugin |
