Skip to content

Workspaces

domainlint automatically detects npm, pnpm, and yarn workspaces. When run from a workspace root, it lints each package independently.

Terminal window
# From the monorepo root — lints all packages
domainlint check

Detection sources (checked in order):

  1. pnpm-workspace.yaml — reads the packages: list
  2. package.json "workspaces" field — supports both npm and yarn formats

Packages without a src/ directory (or whatever srcDir is configured to) are automatically skipped.

Each package uses its own domainlint.json and tsconfig.json if present, falling back to defaults.

Workspace (pnpm)
✓ @my/core (packages/core) 42 files, 150ms
✗ @my/ui (packages/ui) 28 files, 90ms
3 violations
- @my/docs (docs) - skipped: No src/features directory found
Analyzed 70 files across 2 packages in 240ms
✗ 3 total violations found

Use --verbose to see individual violations per package.

You can restrict which packages are allowed to import from each other using packageRules in the workspace root domainlint.json:

{
"packageRules": [
{
"from": "packages/core",
"deny": ["packages/feature-*"]
},
{
"from": "packages/shared",
"deny": ["packages/feature-*", "packages/app"]
}
]
}

Both from and deny use glob patterns matched against package paths relative to the workspace root.

Violation code: noPackageImport

packages/core/src/service.ts:5:1 noPackageImport
Package "packages/core" is not allowed to import from "packages/feature-auth" (@myorg/feature-auth)

domainlint automatically detects circular dependencies between workspace packages. No configuration needed — if package A imports package B and package B imports package A, a cycle is reported.

Violation code: noPackageCycle

packages/core/src/service.ts:3:1 noPackageCycle
Package cycle detected: packages/core -> packages/auth -> packages/core

You can write custom workspace-level rules alongside your module-level custom rules. Export a workspaceRules array from your domainlint.rules.ts:

import type { WorkspaceRule } from "domainlint";
export const workspaceRules: WorkspaceRule[] = [
{
name: "no-app-to-lib",
check({ edges, emitViolation }) {
for (const edge of edges) {
if (edge.fromPackage.startsWith("apps/") && edge.toPackage.startsWith("libs/internal")) {
emitViolation({
file: edge.file,
line: edge.line,
col: edge.col,
message: `App "${edge.fromPackage}" should not import internal lib "${edge.toPackage}"`,
});
}
}
},
},
];

The WorkspaceRuleContext provides:

PropertyTypeDescription
packagesWorkspacePackageInfo[]All workspace packages with name, path, and relPath.
edgesPackageImportEdge[]All cross-package import edges with fromPackage, toPackage, file, specifier, line, col.
packageRulesPackageImportRestriction[]The deny rules from config (useful if you want to build on them).
emitViolation(result) => voidReport a violation.

Use packageRulesFile in domainlint.json to load workspace rules from a custom path:

{
"packageRulesFile": "config/workspace-rules.ts"
}