VisCommandsvis deps

vis deps

Lint workspace dependency policies (workspace-protocol, banned-deps, redefine-root, workspace-versions, custom-types)

vis deps

Workspace-wide dependency policy linter. Reads every package.json in the workspace and reports drift, banned deps, and redefinitions against the root. --fix rewrites files in place.

vis deps

Running with no flags enables every check at once. Pass a specific flag to scope the run.

Usage

vis deps [options]

Examples

# Run every enabled lint and exit non-zero on failures
vis deps

# Only check that internal deps use workspace:*
vis deps --workspace-protocol

# Auto-rewrite internal deps to use workspace:*
vis deps --workspace-protocol --fix

# Flag deps duplicated between root and child packages
vis deps --redefine-root

# Flag deps matching policy.bannedDeps in vis config
vis deps --banned-deps

# One-off ban: flag any package declaring left-pad or request
vis deps --ban left-pad --ban request

# Flag external deps declared at different versions across packages
vis deps --workspace-versions

# Rewrite drifting deps to the highest sibling version
vis deps --workspace-versions --fix

# One-off pin: flag any package declaring react at a different version
vis deps --pin react@18.2.0

# Rewrite drifting deps to catalog: when a catalog already pins them
vis deps --workspace-versions --resolve catalog --fix

# Suggest new catalog entries for deps ≥3 packages already agree on
vis deps --workspace-versions --resolve catalog --propose-min 3

# Flag drift in engines.{node,pnpm}, packageManager, volta.{node,pnpm,yarn}, devEngines
vis deps --custom-types

# Align all engines/packageManager/volta versions to the highest sibling
vis deps --custom-types --fix

# Emit findings as JSON for CI / editor integrations
vis deps --format json

Options

OptionDefaultDescription
--workspace-protocolfalseLint that internal deps use the workspace: protocol
--redefine-rootfalseLint that no child re-declares a dep already pinned in the workspace root
--banned-depsfalseLint deps against policy.bannedDeps in vis config
--workspace-versionsfalseLint that all packages declare external deps at the same version
--custom-typesfalseLint engines.{node,pnpm}, packageManager, volta.*, devEngines.* for drift across packages
--empty-depsfalseFlag empty dependency blocks (dependencies: {}, devDependencies: {}, …)
--root-privatefalseEnsure the workspace root package.json sets "private": true
--root-package-managerfalseEnsure the workspace root package.json declares a packageManager field
--root-depsfalseFlag runtime dependencies on the private workspace root (move them to devDependencies)
--missing-package-jsonfalseFlag workspace directories that lack a package.json
--dead-workspace-patternsfalseFlag workspace patterns that match zero packages
--types-in-depsfalseFlag @types/* declared in dependencies on a private package (should be devDependencies)
--similar-depsfalseFlag version drift across related dep families (react+react-dom, @babel/*, @storybook/*, …)
--depRestrict --workspace-versions/--custom-types to a single dep
--banBan a dep name or glob for this run (repeatable). Auto-enables --banned-deps
--pinPin a dep to an exact specifier for this run, e.g. react@^18.2.0 (repeatable)
--resolveConflict resolution: highest, lowest, or catalog (default: highest)
--propose-minPropose catalog entries for deps ≥N packages already agree on. Activates with --resolve catalog
--fixfalseAuto-fix violations in place (writes package.json files)
--fix-specifierSpecifier used by --fix for workspace-protocol (default: workspace:*)
--formathumanOutput format: human, json, or minimal
--quietfalseSuppress all output except errors

Lint kinds

--workspace-protocol

Every internal dep — one whose name matches a workspace package — must use workspace:* (or workspace:^ / workspace:~ / a workspace-relative range). Catches the case where a package was extracted from the registry to the workspace but its consumers still pin the registry version.

--redefine-root

Flags every dep that appears in both the workspace root package.json and a child package's package.json. The root pin is canonical; redefining it in a child either masks the root version or silently drifts.

--banned-deps

Reads policy.bannedDeps (or --ban <name> repeatable) and flags every package that declares any of them. Glob support (legacy-*, **deprecated**).

Each rule is either a plain reason string or an object with { reason, replacement?, packages?, paths? }. Optional packages (globs over the declaring package's name) and paths (globs over the workspace-relative packageDir) narrow where the rule applies; with both set, either match is enough. Omit both to ban anywhere — the default. Exact-name keys still beat globs, but only among rules whose scope matches the candidate dep.

policy: {
    bannedDeps: {
        request: "deprecated; use undici",
        moment: { reason: "huge bundle, frozen upstream", replacement: "date-fns" },
        // Apply only inside shared libs.
        react: { reason: "no react in shared libs", paths: ["packages/shared/**"] },
        // Apply only to apps.
        next: { reason: "apps only", packages: ["@app/*"] },
    },
},

--workspace-versions

Detects "the same dep declared at different versions across packages." Default policy is highest-semver-wins per dep across dependenciesdevDependenciespeerDependencies. With --resolve catalog, drifting deps are rewritten to catalog: when a sibling catalog already pins them; with --propose-min N, new catalog entries are suggested for deps that ≥N packages already agree on.

--custom-types

Detects drift in version pins that live outside *Dependencies blocks. Five built-in customTypes:

customTypeSource
enginespkg.engines.{node,pnpm,yarn,npm,...}
voltapkg.volta.{node,pnpm,yarn}
packageManagerpkg.packageManagername@version or name@version+sha512.<hash>
devEngines.runtimepkg.devEngines.runtime (single object or {name, version}[])
devEngines.packageManagerpkg.devEngines.packageManager (same shape)

Each (customType × depName) cluster is tracked independently — engines.node and volta.node do not cross-couple. Use a versionGroup once that lands if you need to enforce they agree.

--fix rewrites in place. The +sha512.<hash> integrity suffix on packageManager is dropped on bump — content-integrity hashes are tied to a specific tarball, not a version, so users must regenerate via Corepack:

vis deps --custom-types --fix
corepack use pnpm@10.32.1   # regenerate the +sha512 hash

User-defined customTypes (policy.customTypes.extraTypes)

When a tool stores version pins somewhere built-in customTypes don't reach (e.g. legacy pnpm.overrides, a private toolchain block, an in-house resolver field), declare it under policy.customTypes.extraTypes. Entries are layered on top of the built-ins — they never replace engines/volta/packageManager etc.

Pick a strategy matching how the version is encoded at the path:

strategyShape at pathWhen to use
versionsByName{ "<name>": "<version>", ... }A block of name → version, like pnpm.overrides or engines.
name@version"<name>@<version>" (string)Single-string slots like packageManager (pnpm@10.32.1).
name~version"<name>~<version>" (string)Tilde-separated single-string slots (mirrors syncpack's tilde form).
string"<version>" (bare)A bare version string. Requires depName so we know what to call it.
export default {
    policy: {
        customTypes: {
            extraTypes: [
                // Legacy pnpm.overrides — versionsByName at pnpm.overrides.
                { name: "pnpmOverridesLegacy", path: "pnpm.overrides", strategy: "versionsByName" },
                // Private toolchain pin: { runtime: "node@22.14.0" }
                { name: "myToolPin", path: "myTool.runtime", strategy: "name@version" },
                // Tilde-separated pin: { runtime: "node~22.14.0" }
                { name: "myToolTildePin", path: "myTool.tildeRuntime", strategy: "name~version" },
                // Bare version string: { minNode: "22.14.0" } — needs depName.
                { name: "minNode", path: "config.minNode", strategy: "string", depName: "node" },
            ],
        },
    },
};

A name that collides with a built-in (engines, volta, packageManager, devEngines.runtime, devEngines.packageManager) is rejected with a non-zero exit — the built-in stays canonical. Missing/duplicate name, missing path, invalid strategy, or strategy: "string" without depName all fail validation up front before the workspace is scanned.

path always splits on . literally — there is no escape mechanism, so a single package.json key that itself contains a . (e.g. pkg["foo.bar"]) cannot be reached. Use a parent-then-child layout if you control the schema.

Overlap with vis doctor

  • vis doctor verifies the installed runtime matches engines.node (machine-side: "is my Node binary the right version?").
  • vis deps --custom-types verifies all packages declare the same engines.node (workspace-side: "do all my package.json files agree?").

Both ship; both useful. Run vis doctor on a developer machine to catch local mismatches; run vis deps --custom-types in CI to catch workspace drift.

Configuration

Set defaults under policy in your vis.config.ts (or vis.json):

export default {
    policy: {
        bannedDeps: ["left-pad", "request", "lodash.*"],
        workspaceProtocol: {
            // true | false | "prompt" — three-state autofix opt-out.
            autofix: true,
        },
        workspaceVersions: {
            autofix: true,
            // Per-rule resolve override; CLI --resolve still wins.
            resolve: "highest",
            ignore: ["@types/node"],
        },
        customTypes: {
            autofix: true,
            ignore: ["bun"],
            resolve: "highest",
            // Optional — see "User-defined customTypes" above.
            extraTypes: [{ name: "pnpmOverridesLegacy", path: "pnpm.overrides", strategy: "versionsByName" }],
        },
    },
};

autofix: false denies the rewrite even when --fix is passed — the run still fails CI, but the file is left alone. Useful when a rule is inherently human-judgement (e.g., banning latest shouldn't be auto-fixed). autofix: "prompt" is reserved for future interactive UX; treats the run as report-only today.

Output formats

Human (default)

Color-coded sections per lint kind, grouped by ${customType} ${depName} for custom-types, and by ${depName} for workspace-versions.

JSON

Machine-readable output for CI / editor integrations:

{
    "fixed": {
        "catalogProposals": false,
        "customTypes": false,
        "workspaceProtocol": false,
        "workspaceVersions": false
    },
    "customTypes": {
        "issues": [
            {
                "customType": "engines",
                "depName": "node",
                "specifier": "20.0.0",
                "fix": "22.14.0",
                "packageName": "@my/b",
                "packageJsonPath": "packages/b/package.json",
                "canonicalSource": "@my/a"
            }
        ],
        "total": 1
    }
}

packageJsonPath is always workspace-relative for portability across CI runners.

Minimal

Tab-separated lines, one per issue, suitable for awk/cut pipelines:

custom-types	packages/b/package.json	engines	node	20.0.0 → 22.14.0
workspace-versions	packages/b/package.json	dependencies	react	^18.0.0 → ^18.2.0

Exit codes

  • 0 — no issues, or all issues were auto-fixed
  • 1 — issues found and not fixed (or autofix denied by policy.*.autofix: false)

CI typically runs vis deps with no --fix flag; the non-zero exit fails the build on drift. --fix is for local developer machines and pre-commit hooks.

Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues