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 🙏

© 2026 – Pkg Stats / Ryan Hefner

enablement-build-monorepo-version

v2.0.9

Published

This detects changes in the children packages of a monorepo.

Readme

enablement-build-monorepo-version

Detects which packages in a monorepo have changed by hashing their source folders, computes the next semantic version for each changed package, and emits Azure DevOps pipeline variables. Dependency propagation is recursive — changing a leaf package automatically marks all transitive dependents as changed too.

Usage

npx enablement-build-monorepo-version@latest [flags]

How it works

  1. Hash — Each subfolder under the configured children directories is hashed (source files only; node_modules, dist, etc. are excluded).
  2. Compare — Hashes are diffed against saved state from {configDir}/manifest.json when present (cicd[packageName].hash), falling back to {configDir}/hash.json for legacy repos.
  3. Propagate — Any package whose hash changed is marked CHANGED. All transitive dependents (read from {configDir}/manifest.json dependencies when available, or from --dependencies when explicitly provided) are also marked CHANGED via a breadth-first traversal.
  4. Version — The next semver is determined from the current version found in each package's version manifest (package.json, version.json, or pyproject.toml) using conventional-commit rules.
  5. Output — Azure DevOps ##vso[task.setvariable] lines are written to stdout for consumption by downstream pipeline steps.

CLI flags

| Flag | Default | Description | | ----------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | --hash | off | Compute and save current hashes to the hash state file. Must be run before --changed / --version to establish a baseline. | | --changed | off | Print the list of changed packages and emit a changed pipeline variable. | | --version | off | Emit a pipeline variable per changed package containing its next version. | | --save | off | Write the bumped version back into each changed package's version manifests (package.json, version.json, and/or pyproject.toml). Requires --version. | | --tag | off | Create a git tag (<short-name>/<version>) for each changed package, where short-name is the npm package name with its scope prefix removed (e.g. @scope/lib-foolib-foo/1.2.3). | | --retag | off | Convert existing tags that use the old folder-name format to the new short-package-name format. Non-destructive: old tags are left in place. | | --init | off | Inject release:* scripts into the root package.json of the target project. Adds scripts that are missing; strips --children from any that already exist. Combine with --try to preview changes. | | --try | off | Dry-run mode. Prints what each operation would do without writing any files, creating tags, or modifying any manifests. Combine with any other flags to preview their effect. | | --debug | off | Verbose logging of hashes, comparisons, and version resolution. | | --children <dirs> | auto-detect | Comma- or space-separated list of top-level directories to scan (e.g. components,saas). When omitted, directories are read from pnpm-workspace.yaml or package.json workspaces automatically. Falls back to packages if neither file is found. | | --platform <name> | auto-detect | Force the CI output format: ado (Azure DevOps) or github (GitHub Actions). Auto-detected from the presence of a .github/ directory. | | --prefixPath <path> | ./ | Root path prepended to all file lookups. Useful when running from a different working directory. | | --hashFile <path> | auto-detect | Path (relative to prefixPath) where hash state is stored between runs. Defaults to .github/manifest.json or .cicd/manifest.json when those files exist (hashes stored inside cicd[packageName].hash); falls back to .github/hash.json or .cicd/hash.json for legacy standalone hash files. | | --dependencies <path> | dependencies.json | Path to the NX-style dependency graph used for transitive change propagation. By default, manifest dependencies are used when {configDir}/manifest.json exists; passing this flag explicitly overrides that and forces file-based dependency input. | | --hashExcludeFolders <list> | see below | Comma-separated folder names to skip when hashing. | | --hashExcludeFiles <list> | see below | Comma-separated file names to skip when hashing. | | --hashFiles <list> | — | Comma-separated individual file paths to include in the hash state (outside of children folders). |

Default excluded folders: node_modules, coverage, dist, bin, obj, __pycache__, .vs, .nx, .vscode, .idea, .git, .github, .azuredevops, .release

Default excluded files: .npmrc, CHANGELOG.md, README.md


Supported version manifests

Each package directory is scanned for a version manifest in this priority order:

| File | Format | Version field | | ---------------- | ----------------------------------- | -------------------------------------------------------- | | package.json | JSON | "version": "1.2.3" | | version.json | JSON (same shape as package.json) | "version": "1.2.3" | | pyproject.toml | TOML | version = "1.2.3" under [project] or [tool.poetry] |

The first file found is used to determine the current version. When --save is enabled, all existing manifests in this set are updated. If none exists the package is treated as version 0.0.0.


Version bump rules

Versions follow semver and are bumped based on the current version in the package's version manifest:

| Trigger | Bump | Example | | ------------------------------------------------ | --------------- | ----------------- | | fix: prefix in any recent commit message | Patch (0.0.x) | 1.2.31.2.4 | | feat: prefix in any recent commit message | Minor (0.x.0) | 1.2.31.3.0 | | BREAKING anywhere in any recent commit message | Major (x.0.0) | 1.2.32.0.0 |

Currently the version bump always applies a patch increment — conventional-commit scanning via git log is stubbed. The bump logic is in src/version.mjs.


Pipeline variable output

The output format is auto-detected from the presence of a .github/ directory at the root. It can also be forced with --platform ado or --platform github.

Azure DevOps (default when no .github/ directory)

Variables are written to stdout using the ##vso task command:

##vso[task.setvariable variable=<safeName>;isoutput=true;]<version>
##vso[task.setvariable variable=changed;isoutput=true]["pkg-a","pkg-b"]
##vso[task.setvariable variable=components;isoutput=true]true

GitHub Actions (when .github/ directory is present)

Variables are written to the $GITHUB_OUTPUT file (falls back to stdout if the env var is not set):

safeName=<version>
changed=["pkg-a","pkg-b"]
components=true

Consume them in a subsequent step with ${{ steps.<step-id>.outputs.<name> }}.


Variable reference

| Variable | Value | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | <safeName> (per changed package) | Next version for changed packages; previous version for unchanged ones. safeName is the folder name with -, _, and . removed. | | changed | JSON array of changed package folder names. | | <folderName> | true for each top-level folder containing at least one changed package. |


Dependency Source

By default, transitive dependency propagation uses dependencies from {configDir}/manifest.json when that manifest exists.

To force file-based dependency input, pass --dependencies <path>. The file must be an NX project graph export. The relevant section is graph.dependencies, where each key is a package name and its value is an array of { source, target } edges:

{
  "graph": {
    "dependencies": {
      "@scope/pkg-a": [
        {
          "source": "@scope/pkg-a",
          "target": "@scope/shared-lib",
          "type": "static"
        }
      ]
    }
  }
}

Generate it from an NX workspace with:

nx graph --file=dependencies.json

If the file is missing or the --dependencies path doesn't exist, change propagation is skipped and only directly-changed packages are reported (a warning is printed).


Typical pipeline usage

# Step 1 — establish baseline hashes (run once, commit the result)
# Workspace directories are auto-detected from pnpm-workspace.yaml / package.json workspaces
- script: npx enablement-build-monorepo-version --hash

# Step 2 — detect changes and emit version variables for downstream steps
- script: npx enablement-build-monorepo-version --changed --version
  name: versions

# Step 3 — consume a variable from step 2
- script: echo "$(versions.mypackagename)"

To bump and persist the new versions back to the package version manifests (package.json, version.json, and pyproject.toml when present):

npx enablement-build-monorepo-version --changed --version --save

When --changed --version --save detects any changed package, the root package.json version is also bumped by a patch increment.

To override the auto-detected directories, pass --children explicitly:

npx enablement-build-monorepo-version --changed --version --children components,saas

Bootstrapping a new repo

Run --init once from the root of any monorepo to inject the standard release:* scripts into its package.json:

npx enablement-build-monorepo-version --init

Preview what would change without writing anything:

npx enablement-build-monorepo-version --init --try

The following scripts are injected (skipped if already present; --children stripped if already there):

| Script | Command | | ------------------ | ----------------------------------- | | release:try | dry-run of the full release flow | | release:changed | list changed packages | | release:version | bump and save versions | | release:finalize | write hashes | | release:retag | migrate tags to package-name format |


Development

src/
├── index.mjs        CLI entry point and main() orchestration
├── lib.mjs          Core logic (exported for testing)
├── version.mjs      Semver bump logic
└── folder-hash.mjs  Recursive folder hashing

tests/
├── lib.test.mjs     Unit tests for lib.mjs
├── dependencies.json  Sample NX dependency graph fixture
├── package.json       Sample package.json fixture
├── version.json       Sample version.json fixture
└── pyproject.toml     Sample pyproject.toml fixture

Run the test suite:

pnpm test

Watch mode:

pnpm test:watch