GitHub Packages · MIT · Node ≥ 18

monorepo-gauge

Hygiene checks for npm, yarn and pnpm workspaces: circular internal deps, mismatched shared deps, version drift and missing fields.

Hygiene checks for npm/yarn/pnpm workspaces: circular internal dependencies, internal ranges that no longer match, shared dependency drift, duplicate names, public packages depending on private ones, missing package fields and version drift. Text/JSON/SARIF/Markdown, allowlist with expiry, GitHub Action. Zero deps.

  1. 1 Point the @mrlouiswright scope at GitHub Packages in .npmrc

    @mrlouiswright:registry=https://npm.pkg.github.com
  2. 2 Install

    npm i -D @mrlouiswright/monorepo-gauge
Installing from GitHub Packages needs a token

Published to GitHub Packages, not npmjs.com. GitHub Packages needs a GitHub token with read:packages to install, even for public packages: add //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} to .npmrc or run npm login --registry=https://npm.pkg.github.com.

~/project — zsh
$ npx monorepo-gaugemonorepo-gauge — 6 workspace package(s)pnpm workspaces from pnpm-workspace.yaml: a, b, c, dup, dup, priv CRITICAL packages/dup2/package.json "dup" is the name of 2 workspaces: packages/dup1, packages/dup2 [duplicate-package-name]         ↳ fix: Give each workspace a unique name.HIGH     packages/a/package.json circular dependency: a → b → c → a [circular-dependency]         ↳ fix: Break the cycle by extracting the shared code into a new package or inverting one dependency.HIGH     packages/b/package.json b dependencies: "c": "^2.0.0" does not match the local c@1.0.0 [internal-range-mismatch]         ↳ fix: Use "^1.0.0" or "workspace:^"; otherwise the package manager installs c from the registry instead of linking it.MEDIUM   packages/c/package.json react: ^18.2.0 (a) vs ^17.0.2 (c) [dependency-major-drift]         ↳ fix: Align every package on one major (e.g. "^18.2.0"); mixed majors install duplicate copies and break singletons.
Sample output from the monorepo-gauge README.

Why it exists

Monorepos rot quietly. A package bumps to 2.0.0 while a sibling still asks for “^1.0.0”, so the package manager fetches the old copy from the registry instead of linking the local one. One app moves to React 18 and another stays on 17. Two packages end up importing each other. A published package depends on a private one that users can never install. monorepo-gauge reads your workspace layout and reports these problems with the file, a severity and a concrete fix.

CLI, library and GitHub Action. Node.js 18+, zero runtime dependencies. By Louis Wright.

Features

Workspace discovery

package.json workspaces (array or { packages }), pnpm-workspace.yaml and lerna.json, with *, ** and !exclusions. The package manager is detected from the lockfile, .yarnrc.yml and packageManager.

Internal graph

cycles through dependencies/peerDependencies/optionalDependencies (high) and through devDependencies (low), each reported once as a → b → c → a. Internal ranges are checked against the local version with a proper SemVer range matcher (^, ~, x-ranges, hyphen ranges, ||, workspace:^1.2.0). Published packages depending on private ones are caught.

Shared dependency drift

the same external dependency with different majors (medium) or different ranges in the same major (low) across packages and the root, listing who uses what.

Package fields

required fields for publishable packages (default name, version, description, license, repository), repository.directory pointing at the right folder, and license/engines.node/type consistency. Under fixed versioning (lerna version, or --versioning fixed) package versions must match.

Output

text, JSON, SARIF 2.1.0 and Markdown; --fail-on; exit codes 0/1/2; allowlist entries with expiry dates.

Usage

# run at the monorepo root

 npx monorepo-gauge

# Example

 npx monorepo-gauge --fail-on medium

# all publishable packages share one version

 npx monorepo-gauge --versioning fixed

# Example

 npx monorepo-gauge --ignore-packages "playground-*,examples/**"

CI / GitHub Action

permissions:
  contents: read
  security-events: write   # only needed for the SARIF upload
jobs:
  monorepo-gauge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with: { persist-credentials: false }
      - uses: MrLouisWright/monorepo-gauge@v1
        with:
          fail-on: high
          sarif: monorepo-gauge.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: monorepo-gauge.sarif }

Options & flags

FlagDescription
Workspace
--versioning <mode>fixed | independent (default: from lerna.json, else independent)
--require <fields>Required fields for publishable packages (default: name,version,description,license,repository)
--ignore-packages <g>Package names or dirs to skip (globs, comma-separated)
--ignore-deps <g>External dependencies to skip in drift checks
--include-peersInclude peerDependencies in drift checks
Common
-c, --config <file>JSON config (default: .monorepo-gauge.json or "monorepoGauge" in package.json)
-f, --format <fmt>text (default) | json | sarif | markdown
--json <file>Also write a JSON report
--sarif <file>Also write SARIF 2.1.0
--markdown <file>Also write Markdown (e.g. for a job summary)
--fail-on <sev>critical | high | medium | low | info | none (default: high)
--list-rulesPrint rules and default severities
--no-fixesHide fix suggestions in text output
--no-colorDisable colours

Full reference, config file format and API in the README.

Support this project

Support open-source security tools

I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.

Buy me a coffee (opens Stripe in a new tab)

Secure checkout by Stripe

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20