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.
GitHub Packages · MIT · Node ≥ 18
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 Point the @mrlouiswright scope at GitHub Packages in .npmrc
@mrlouiswright:registry=https://npm.pkg.github.com2 Install
npm i -D @mrlouiswright/monorepo-gaugePublished 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.
$ 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.
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.
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.
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.
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.
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.
text, JSON, SARIF 2.1.0 and Markdown; --fail-on; exit codes 0/1/2; allowlist entries with expiry dates.
# 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/**"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 }
| Flag | Description |
|---|---|
| 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-peers | Include 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-rules | Print rules and default severities |
--no-fixes | Hide fix suggestions in text output |
--no-color | Disable colours |
Full reference, config file format and API in the README.
Support this project
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