Workspaces

Fallow detects and analyzes monorepo workspaces automatically. It understands npm, yarn, pnpm, and Deno workspace protocols, resolves cross-package imports through node_modules symlinks or Deno import maps, and supports scoping output to a single package.

Workspace detection is automatic. If your repo has a workspaces field in package.json, a pnpm-workspace.yaml file, or a workspace field in a root deno.json / deno.jsonc, fallow will find and analyze all packages.

Workspace auto-detection

Fallow detects workspaces from these sources:

SourceExample
package.json workspaces field"workspaces": ["packages/*"]
pnpm-workspace.yamlpackages: ["packages/*", "apps/*"]
deno.json / deno.jsonc workspace field"workspace": ["./packages/*"]
{
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}

Deno workspaces

A root deno.json or deno.jsonc drives workspace discovery the same way package.json does for npm. The workspace field lists member globs and accepts both the bare array form and the object form:

{
  "workspace": ["./apps/*", "./packages/*"]
}

Each member's name and exports are read like their package.json equivalents, so @scope/lib with "exports": { ".": "./mod.ts" } resolves exactly like an npm package export map. When a member directory contains both a package.json and a deno.json, the package.json wins for package identity.

Import maps (imports in root or member configs) resolve per package: an exact specifier match wins, otherwise the longest matching slash-prefix entry applies. Deno workspace member names count as listed dependencies, so importing a sibling package by name is not reported as an unlisted dependency.

A pure Deno root (no root package.json) does not warn about a missing node_modules directory. A malformed root deno.json / deno.jsonc is an explicit analysis error (exit 2) rather than a silently empty workspace list.

Cross-workspace imports

When apps/web imports from @myorg/ui, fallow resolves the import through the node_modules symlink back to the workspace source files. Symlinked paths are canonicalized, so usage is tracked against the real source location, not the node_modules copy.

// Resolves to packages/ui/src/Button.tsx via node_modules symlink
import { Button } from '@myorg/ui'

Pnpm content-addressable store

Pnpm uses a content-addressable store with a .pnpm virtual store inside node_modules. Fallow detects these paths and maps them back to the original workspace source files. Cross-package usage is tracked correctly even with pnpm's strict isolation mode.

Package exports resolution

Fallow resolves the exports field in each package's package.json, including subpath patterns:

{
  "name": "@myorg/ui",
  "exports": {
    ".": "./src/index.ts",
    "./icons": "./src/icons/index.ts",
    "./theme/*": "./src/theme/*.ts"
  }
}

An import of @myorg/ui/icons resolves to packages/ui/src/icons/index.ts.

Output directory mapping

When a package's exports or main points to a build output directory (dist, build, out, esm, cjs), fallow maps it back to the corresponding source file. It tries the same relative path under src/ with source extensions (.ts, .tsx, .js, .jsx).

{
  "main": "dist/index.js"
}

Fallow maps dist/index.js back to src/index.ts (or .js, .tsx, etc.) if the source file exists.

Per-package tsconfig.json

Fallow discovers tsconfig.json files in each workspace package automatically. Path aliases from each package's tsconfig.json are used when resolving imports within that package.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

Scoping output to workspaces

Use -w / --workspace to focus on one or more workspaces. Fallow still analyzes the full monorepo graph; only the reported issues are scoped.

Patterns support three forms:

  • Exact package name or workspace path: web, @myorg/ui, apps/web
  • Glob matched against BOTH the package name AND the workspace path relative to the repo root: apps/*, @scope/*
  • Negation with a ! prefix: !apps/legacy

Values can be comma-separated or the flag repeated. Quote patterns containing ! or glob characters so your shell doesn't expand them.

# Single package (back-compat with earlier fallow versions)
fallow dead-code -w @myorg/ui

# Multiple packages (comma-separated or repeated)
fallow dead-code -w web,admin
fallow dead-code -w web -w admin

# Glob: all apps, all scoped packages
fallow dead-code -w 'apps/*'
fallow dead-code -w '@myorg/*'

# Everything under apps/ except legacy
fallow dead-code -w 'apps/*,!apps/legacy'

# Everything except a workspace (negation only)
fallow dead-code -w '!apps/legacy'
 Unused exports (2)
  apps/web/src/Button.tsx
    :15 ButtonVariant
  apps/admin/src/Modal.tsx
    :8  ModalContext
  Exported symbols with zero references

 2 issues (31ms, 1,247 files)

For CI matrix jobs, combine --workspace with --changed-since: fallow intersects the two filters so you analyze only changed files inside the matched workspaces.

Additional workspace patterns

If fallow doesn't detect all your workspace packages automatically, add extra patterns in your config:

{
  "workspaces": {
    "patterns": ["tools/*", "shared/*"]
  }
}

These patterns are additive. They extend whatever fallow already detects from package.json, pnpm-workspace.yaml, or a root Deno config.

See also