npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

dep-fence

v0.6.1

Published

A tool to safeguard package dependencies and TypeScript configuration hygiene in monorepos, based on rich policy examples and explicit reasons

Downloads

32

Readme

dep-fence 🧱✨

npm version [license]

A tool to safeguard package dependencies and TypeScript configuration hygiene in monorepos, based on rich policy examples and explicit reasons. Every finding explains why a rule must or should apply, keeping reviews focused and predictable. This helps automate reviews, clarify next steps, and connect findings directly to coding-assistant AI instructions. 🚥

Table of Contents 🧭


What, Why & How 💡

  • What it is: A lightweight, policy‑driven guardrail for repository‑wide dependency boundaries. It detects boundary crossings and can fail CI. Typical use: enforce public‑API‑only imports, keep UI/domain layers separate, align peerDependencies with bundler externals, keep tsconfig sane, and govern skipLibCheck — always with an explicit “Because: …”.
  • Problems it solves: deep import leaks, accidental cross‑package coupling in monorepos, type/exports drift that breaks publishing, peer vs bundler external mismatches, JSX option inconsistencies, and more.
  • How it compares and when to use which:
    • ESLint: great for per-file static analysis and style/bug-catching. Use ESLint for in-file concerns (optionally with rules like import/no-internal-modules or no-restricted-imports); use dep-fence for cross-package boundaries and repo-level policies.
    • Knip: finds unused/missing dependencies and unused files/exports, with first-class monorepo support. Use Knip for dependency inventory & dead-code surfacing; use dep-fence for boundary policies and CI gating — they complement each other.
    • dependency-cruiser / madge: visualize and validate dependency graphs. Use them for exploration and complex graph rules; use dep-fence for an opinionated, CI-first policy engine with simple allow/forbid semantics.
    • syncpack: keeps versions and workspace ranges consistent across a monorepo. Use syncpack for manifest hygiene; use dep-fence for runtime/build-time import and peer/bundler alignment.
    • publint: lints the published package surface for environment compatibility and common mistakes. publint protects consumers; dep-fence keeps sources respecting boundaries before you publish.
    • (optional) Are the Types Wrong? (attw): validates TypeScript type resolution/exports of published output; pairs well with publint in release pipelines.

Why dep‑fence? ✨

  • Condition‑driven rules (e.g., apply only to packages that are UI + publishable).
  • Prevent double‑bundling by aligning tsup external with peerDependencies. 📦➕📦❌
  • Govern skipLibCheck with explicit reasons or an allow‑list. ⚖️
  • Keep tsconfig healthy (baseline inheritance, forbid ../src direct references, JSX option sanity).
  • Every message includes “Because: …”, making policy intent visible. 🗣️

It works alongside your existing linters, graph analyzers, dependency inventory tools, and package-publish validators. Together they cover in-file quality, dependency hygiene, and release readiness — while dep-fence enforces cross-package boundaries and provides CI-first policy guardrails.

How it works? 🧭

  • Detects repo root via pnpm-workspace.yaml or nearest package.json.
  • Scans package directories:
    • packages/*/**/package.json
    • app/package.json (if present)
  • Infers attributes: ui, publishable/private, usesTsup, hasTsx, browser/node, worker, next, storybook, app.
  • Derives tsup.external from repo tsup.base.config.* and per‑package tsup.config.* when available.

Install 📦

Local (recommended):

pnpm add -D dep-fence
# or
npm i -D dep-fence

Global:

npm i -g dep-fence

Requirement: Node.js >= 18


Getting Started 🛣️

CLI Usage

dep-fence                    # YAML output (default)
dep-fence --format pretty    # human‑readable (pretty)
dep-fence --format yaml      # YAML explicitly
dep-fence -f yaml -g severity  # group by ERROR/WARN/INFO
dep-fence -f json            # JSON output
dep-fence --strict           # exit 1 if any ERROR
dep-fence -c path/to/dep-fence.config.ts  # explicit policy file (TS/JS supported)
dep-fence -h                 # help
dep-fence -v                 # version

Output Formats

  • Default: YAML grouped by package (equivalent to --format yaml --group-by package).
  • Pretty: --format pretty for human‑readable, package‑grouped output.
  • JSON: --format json for machine‑readable output.
  • YAML grouping: --group-by <package|rule|severity> only affects YAML.
    • Examples:
      • dep-fence --format yaml --group-by rule
      • dep-fence --format yaml --group-by severity

Short flags: -f = --format, -g = --group-by, -c = --config.

Note: the legacy --json flag was removed; use --format json.

Commands and expected output:

pnpm dep-fence
pnpm dep-fence --strict  # CI gate (exit 1 on ERROR)

Success Output

✔ No violations (0)

Violation Output in YAML format (default):

"@your/ui-button":
  - type: ui-in-deps
    severity: ERROR
    message: |
      UI libs should be peerDependencies (not dependencies):
      - react
    reason: UI packages should not bundle React/MUI; rely on host peers.

Violation Output in pretty format (with --format pretty option):

=== @your/ui-button ===
ERROR ui-in-deps: UI libs should be peerDependencies (not dependencies):
- react
Because: UI packages should not bundle React/MUI; rely on host peers.

Zero‑Config Mode 🚀

Zero‑Config Mode runs the default package policies (package‑level checks) in a typical monorepo:

  • Auto‑detects repo root via nearest pnpm-workspace.yaml or package.json.
  • Scans packages under packages/*/**/package.json and app/package.json (if present).
  • Applies sensible default policies that cover UI/peer/tsup alignment, tsconfig hygiene, and skipLibCheck governance.
  • No dep-fence.config.* required to get value on day one.

Why this matters (concrete benefits):

  • Instant adoption: drop into CI and get actionable findings without setup.
  • Predictable baselines: the same defaults across repos reduce bikeshedding.
  • Safe by default: read‑only checks; use --strict to fail on ERRORs when ready.
  • Incremental rollout: start as a linter, then codify exceptions as you learn.
  • Reviewer‑friendly: every finding says “Because: …” to explain the policy.

Basic Usage 🖥️

Save a report for review (grouped by severity)

dep-fence -f yaml -g severity > dep-fence.report.yaml

This helps leads/owners scan ERRORs vs WARNs separately and share the file in reviews.

Extract error summaries in CI logs (JSON → jq)

dep-fence -f json | jq -r '.findings[] | select(.severity=="ERROR") | "[\(.severity)] \(.packageName) :: \(.rule)"'

This prints concise error lines like: [ERROR] @your/ui-button :: ui-in-deps.


Advanced Usage 💪

Policy Configuration 🛠️

Zero‑config works out of the box. When you need more control, provide an explicit policy file (dep-fence.config.ts or dep-fence.config.mjs) at the repo root to replace the defaults.

Policy file (TypeScript example):

// dep-fence.config.ts
import { all, isUI, isPublishable } from 'dep-fence/conditions';
import type { Policy } from 'dep-fence/types';

export const policies: Policy[] = [
  {
    id: 'ui-externals-and-peers',
    when: all(isUI(), isPublishable()),
    because: 'UI packages should not bundle React/MUI; rely on peers.',
    rules: ['ui-in-deps', 'ui-missing-peer', 'peer-in-external']
  }
];

You can also tweak severity per rule via severityOverride and compose complex conditions with all(...), any(...), and not(...). Helpers like isUI(), isPublishable(), and usesTsup() are available from dep-fence/conditions.

Repo‑wide operational settings (JSON) can be declared in dep-fence.config.json:

{
  "allowSkipLibCheck": [
    "@your/legacy-chart" // temporary exception
  ]
}

Per‑package justification for skipLibCheck can live in each tsconfig.json:

{
  "compilerOptions": { "skipLibCheck": true },
  "checkDeps": {
    "allowSkipLibCheck": true,
    "reason": "3rd‑party types temporary mismatch; scheduled fix"
  }
}

Policies by Example

Representative Policy Examples (Purpose / Snippet / Outcome)

  • Public API only (ban deep imports across packages)

    • Purpose: @org/foo is OK; @org/foo/src/x is not.
    • Snippet:
      { id: 'public-api-only', when: () => true, because: 'Only use package public entrypoints.', rules: [
        { rule: 'import-path-ban', options: { forbid: ['^@[^/]+/[^/]+/src/'] }, severity: 'ERROR' },
      ]}
    • Outcome: Detects cross‑package references into src/.
  • Peers × tsup externals alignment

    • Purpose: ensure peers are treated as externals by the bundler.
    • Snippet:
      { id: 'tsup-peer-hygiene', when: isPublishable(), because: "Don't bundle peers.", rules: ['peer-in-external','external-in-deps'] }
    • Outcome: Flags peers missing in tsup.external or duplicated in dependencies.
  • Enforce types from dist

    • Purpose: published types should come from dist/*.d.ts.
    • Snippet:
      { id: 'package-types-dist', when: isPublishable(), because: 'Expose types from dist/*.d.ts.', rules: ['package-types-dist'] }
    • Outcome: Violates when types or exports[entry].types do not point to dist/*.d.ts.

Select Pre-Defined Config Files

You can import pre-defined config files in the examples directory with --config option as needed. Please consult examples/README.md for stacks/recipes oriented examples (React, Router v7, TypeScript v5, Bundlers). Legacy examples/policies/ paths have been retired; use the stacks/recipes paths.

Create Your Original Config Files

You can create your own config files to customize the rules and settings.

Simple example 1:

import type { Policy } from 'dep-fence/types';
import { defaultPolicies } from 'dep-fence';
import { pkgUiPeersRule, pkgExportsExistRule, tsconfigHygieneRule } from 'dep-fence/guards';

const custon: Policy[] = [
  pkgUiPeersRule({ exclude: ['@your/app'] }),
  pkgExportsExistRule({ roots: ['packages', 'app'] }),
  tsconfigHygieneRule({
    skipLibCheck: { allowedPackages: ['@your/temp-exception'], requireReasonField: true, action: 'warn' },
  }),
];

const policies: Policy[] = [...defaultPolicies, ...custom];
export default policies;

Simple example 2:

import type { Policy } from 'dep-fence/types';

export const policies: Policy[] = [
  {
    id: 'my-custom-checks',
    when: isPublishable(),
    because: 'repo‑specific validation',
    rules: [{
      rule: 'custom',
      id: 'check-pkg-field',
      run: (ctx) => {
        const f = [] as any[];
        if (!ctx.pkgJson.customField) {
          f.push({ packageName: ctx.pkgName, packageDir: ctx.pkgDir, rule: 'check-pkg-field', severity: 'WARN', message: 'missing customField', because: ctx.because });
        }
        return f;
      }
    }]
  }
];

Create Your Own Policies from Scratch with TS (typed, recommended) or MJS (zero‑setup)

Config format choice (TS or MJS)

  • Supported: dep-fence.config.ts, dep-fence.config.mjs (ESM .mjs preferred over .js).
  • Recommendation: prefer .mjs for zero‑setup CI/offline; prefer .ts for editor type‑safety.
  • Running .ts configs:
    1. Built‑in fallback (no extra deps): strips type‑only syntax and evaluates at runtime.
    2. Loader (e.g., tsx): NODE_OPTIONS="--loader tsx" pnpm dep-fence.
    3. Prebuild (e.g., tsup): tsup dep-fence.config.ts --format esm --dts false --out-dir ..
  • Pitfalls: require Node ≥ 18 with ESM, avoid mixing CJS/ESM, minimize runtime deps in air‑gapped CI.

TS: dep-fence.config.ts

import { defaultPolicies } from 'dep-fence';
import type { Policy } from 'dep-fence/types';

const custom: Policy[] = [
  { id: 'ban-deep-imports', when: () => true, because: 'Use public API only; avoid cross‑package internals.', rules: [
    { rule: 'import-path-ban', options: { forbid: ['^@[^/]+/[^/]+/src/'] }, severity: 'ERROR' },
  ]},
];

export const policies: Policy[] = [...defaultPolicies, ...custom];

MJS: dep-fence.config.mjs

import { defaultPolicies } from 'dep-fence';
/** @type {import('dep-fence/types').Policy[]} */
export const policies = [
  ...defaultPolicies,
  { id: 'ban-deep-imports', when: () => true, because: 'Use public API only; avoid cross‑package internals.', rules: [
    { rule: 'import-path-ban', options: { forbid: ['^@[^/]+/[^/]+/src/'] }, severity: 'ERROR' },
  ]},
];

Environment variables:

  • DEP_FENCE_CONFIG — absolute/relative path to a policies module (overrides file discovery).
  • DEP_FENCE_REPO_CONFIG — path to a JSON file with repo‑wide settings (overrides dep-fence.config.json).

Workspace/subtree overrides

  • Typical setup is a single root config; split into modules or swap via DEP_FENCE_CONFIG for subtrees/teams if needed.

Performance and caching

  • Prefer fewer, broader rules to many tiny ones.
  • In CI, scope checks to changed packages where possible.

Git Integration: pre‑commit / pre‑push 🐙

Alongside package policies (see Zero‑Config Mode), dep‑fence ships lightweight repository‑level guards under the dep-fence/guards entry. They are designed for Git hooks (predictable, no hidden state):

  • allowed-dirs — Commit scope guard: staged files must be under allowed globs.
  • mtime-compare — Advisory: detect files newer than your rules/SSOT baseline.
  • upstream-conflict — Optimistic conflict detection: fail if upstream has other‑author changes touching protected paths since your base.

New guards for monorepo publishing/build hygiene:

  • pkg-exports-exist — Verify package.json main/module/exports paths point to existing files (prevents publish/bundler breakage).
  • pkg-ui-peers — Enforce UI singletons as peers and align bundler externals (flags: ui-in-deps, ui-missing-peer, peer-in-external, external-in-deps).
  • tsconfig-hygiene — Keep tsconfig healthy (extends repo base, jsx option sanity, skipLibCheck governance with allowlist/justification).

Try the examples:

pnpm dlx tsx examples/guards/run.ts --mode pre-commit
pnpm dlx tsx examples/guards/run.ts --mode pre-push

pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.ui-peers.config.ts

pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.pkg-exports.config.ts

pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.tsconfig-hygiene.config.ts

# Additional guard presets (pair with recipes)
pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.ui-peer-policy.config.ts
pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.publishable-tsconfig-hygiene.config.ts
pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.jsx-option-for-tsx.config.ts
pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.tsconfig-paths.config.ts
pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.maplibre-allowlist.config.ts
pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.package-types-dist.config.ts
pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.strict-ui.config.ts
pnpm dlx tsx examples/guards/run.ts --mode pre-commit \
  --config examples/guards/guards.source-import-ban.config.ts

CI Integration 🛡️

# GitHub Actions example
jobs:
  dep-fence:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: pnpm i --frozen-lockfile
      - run: pnpm dep-fence  # refer to your package.json script

Concepts & Terminology 📚

  • Rule
    • Smallest, atomic check that produces findings (e.g., peer-in-external, source-import-ban).
    • Takes per‑rule options and a computed severity; may be built‑in or a plugin (via create(options)=>check(ctx)).
  • Policy
    • A composable unit that targets a package set and explains intent.
    • Shape: { id, when, because, rules, options?, severityOverride? } — evaluated per package.
    • Example: “UI externals and peers” bundles ui-peer-policy rule + peer-in-external rule for publishable UI packages.
  • Recipe
    • A ready‑to‑run policy module focused on one goal; copy‑pasteable.
    • Lives under examples/recipes/…/dep-fence.config.ts and is referenced via DEP_FENCE_CONFIG=….
  • Guard
    • A repository‑level check for Git hooks/CI (pre‑commit/pre‑push). Runs via examples/guards/run.ts.
    • Guards read workspace state (staged files, tsconfig, package.json) and fail fast outside policy evaluation.

Examples 📁

See examples/README.md for a concise, up‑to‑date index of stacks and recipes. Typical invocations:

# Minimal policy set
DEP_FENCE_CONFIG=examples/recipes/minimal/dep-fence.config.ts pnpm dep-fence

# Focused examples (stacks/recipes paths)
DEP_FENCE_CONFIG=examples/recipes/tsconfig-paths/dep-fence.config.ts pnpm dep-fence
DEP_FENCE_CONFIG=examples/recipes/package-exports-guard/dep-fence.config.ts pnpm dep-fence
DEP_FENCE_CONFIG=examples/stacks/bundlers/vite/recipes/multi-entry-workers/dep-fence.config.ts pnpm dep-fence

# With repo‑wide JSON settings
DEP_FENCE_REPO_CONFIG=examples/repo-config/dep-fence.config.json pnpm dep-fence

UI policies — quick guide:

  • ui-peer-policy (recipe): package.json‑only check; fast to adopt; good first step.
  • ui-peers-light (recipe): gentle variant; WARN by default; no bundler checks.
  • minimal (stack): adds bundler externals alignment (tsup) on top of peers.
  • strict-ui (recipe): strict peers + bundler alignment; suitable for CI gating in libraries.

What It Checks 🔍

  • Peers × tsup externals
    • peer-in-external (peer missing from tsup.external)
    • external-in-deps (external also listed in dependencies)
  • UI package hygiene
    • ui-in-deps (React/MUI/Emotion in dependencies)
    • ui-missing-peer (UI libs used but missing from peerDependencies)
  • TypeScript hygiene
    • tsconfig-no-base (not extending repo base; hint)
    • paths-direct-src (direct ../src references)
    • jsx-mismatch (.tsx present but jsx not react-jsx)
  • skipLibCheck governance
    • skipLibCheck-not-allowed (enabled without permission)
    • skipLibCheck-no-reason (permitted but missing rationale)
  • Encapsulation rules
    • maplibre-direct-dep (direct MapLibre deps outside the wrapper package)

All findings include “Because: …” to surface rationale.

Default Policies (catalog) 📚

Each default policy explains why it applies (Because) and targets specific attributes:

  • ui-externals-and-peers — UI packages must not bundle React/MUI; rely on peers. Rules: ui-in-deps, ui-missing-peer, peer-in-external.
  • tsup-peer-hygiene — bundlers must externalize peers. Rules: peer-in-external, external-in-deps.
  • publishable-tsconfig-hygiene — keep published tsconfig clean. Rules: tsconfig-no-base, paths-direct-src.
  • publishable-local-shims — avoid long‑lived local *.d.ts. Rule: local-shims (default WARN; can be raised).
  • jsx-option-for-tsx — TSX requires jsx: react-jsx. Rule: jsx-mismatch.
  • skipLibCheck-governance — permissioned toggle with reasons. Rules: skipLibCheck-*.
  • non-ui-paths-hygiene — discourage cross‑src refs broadly. Rule: paths-direct-src.
  • maplibre-encapsulation — only wrapper package may depend on MapLibre. Rule: maplibre-direct-dep.

Severity override example:

export const policies = [
  {
    id: 'ui-externals-and-peers',
    when: all(isUI(), isPublishable()),
    because: '…',
    rules: ['ui-in-deps', 'ui-missing-peer', 'peer-in-external'],
    severityOverride: { 'ui-missing-peer': 'ERROR' }
  }
];

Opt‑in Extensions (examples) 🧪

Additional rules and helpers can be enabled via a policy file:

  • source-import-ban — ban specific named imports from a module. See examples/recipes/source-import-ban/dep-fence.config.ts.
  • tsconfig-paths — enforce paths to point to dist/*.d.ts and/or forbid patterns.
  • package-exports-guard — guard subpaths (e.g., forbid types for ./workers/*).
  • package-types-dist — ensure package types and exports[entry].types point to dist/*.d.ts.

Programmatic API 🧩

import { runWithPolicies, defaultPolicies } from 'dep-fence';
import { any, isPublishable } from 'dep-fence/conditions';

const findings = runWithPolicies(defaultPolicies);
const hasError = findings.some((f) => f.severity === 'ERROR');

Types are available from dep-fence/types (Finding, Policy, Condition, Severity, ...).

Troubleshooting 🆘

  • False positives with path aliases: align dep‑fence’s resolver with your tsconfig paths/bundler aliases.
  • Type‑only imports flagged: adjust rule targets/conditions or use rules that account for types‑only edges.
  • Dynamic imports: dynamic/computed paths are treated conservatively; model critical boundaries with static paths.

FAQ ❓

  • ESLint or dep‑fence?

    • Both. ESLint covers in‑file quality; dep‑fence enforces cross‑file/package boundaries.
  • Why not just dependency‑cruiser?

    • It’s great for exploration/visualization. dep‑fence focuses on CI‑first, opinionated defaults for monorepos with a small set of high‑signal rules.
  • What are the best practices for using dep-fence?

    • Start with simple, high-impact rules such as banning deep imports and aligning peers with bundler externals.
    • Keep all exceptions justified with clear “Because:” statements. Use severityOverride (e.g., WARN → ERROR) to roll out gradually without blocking early adoption.
    • In CI, run dep-fence with --strict to fail on ERRORs; locally, run without --strict to discover and review issues incrementally.
    • Document exceptions and policies transparently so reviews stay predictable and team members understand why rules apply.
  • How to allow a temporary exception?

    • Use a narrowly scoped policy/condition or severityOverride, and record a Because statement.
  • How to protect publish quality?

    • Pair dep‑fence (boundaries/types path/peer×bundler) with publint (package export surface) in CI.
  • Where does “unused dependency warning” fit?

    • Not shipped by default today. It’s best implemented as a Guard preset (repo‑level, may be slower) or as a plugin Rule if you prefer policy evaluation.
    • Guard approach (recommended): scan import graph vs package.json to flag unused deps (pair it with pre‑commit). See depcheck/build graph tools, or author a custom guard similar to guards.package-types-dist.
    • Policy approach: add a plugin rule unused-deps and include it in a policy for publishable packages. This repo already documents plugin rules in docs/dep-fence-upstream-guide.md.

Author ✍️

Hiroya Kubo [email protected]

License 📄

MIT


Happy fencing! 🧱✨ Add reasons to rules and keep dependencies sane.