dependency-change-report
v3.1.1
Published
Generate dependency change reports between git references
Maintainers
Readme
Dependency Change Report
A tool to analyze dependency changes between different versions of a Node.js project and generate detailed reports with changelogs.
Features
- Compare dependencies between two versions of a repository
- Identify added, upgraded, removed, and modified dependencies
- Generate changelogs for upgraded dependencies by analyzing commit history
- Detect namespace changes in dependencies (e.g., from
packageto@org/package) - Create HTML reports with detailed information
- Track and report errors during changelog generation
Installation
Using npx (Recommended)
No installation required! Run directly with npx from within your git repository:
# Auto-detect versions
npx dependency-change-report auto
# Or compare specific versions
npx dependency-change-report compare v1.0.0 v2.0.0Global Installation (Alternative)
For frequent use, you can install globally:
npm install -g dependency-change-reportThen run with:
# Auto-detect versions
dependency-change-report auto
# Or compare specific versions
dependency-change-report compare v1.0.0 v2.0.0Usage
Command Line Interface
The tool provides two main commands:
Auto Command (Recommended)
Automatically detects versions and generates reports:
# From within a git repository
dependency-change-report auto
# Or specify a repository URL
dependency-change-report auto <github-repo>Compare Command
Compare specific versions:
# From within a git repository (repo URL auto-detected)
dependency-change-report compare <older-version> <newer-version>
# Or specify a repository URL explicitly
dependency-change-report compare --repo <github-repo> <older-version> <newer-version>The tool automatically generates three report formats:
report.json- Raw data in JSON formatreport.html- Web-friendly HTML reportreport.md- Markdown report (perfect for PR comments)report.txt- Plain text report
Examples
# Auto-detect versions and generate all reports (from within a git repo)
dependency-change-report auto
# Compare specific versions (from within a git repo)
dependency-change-report compare v1.0.0 v2.0.0
# Compare specific versions with explicit repo URL
dependency-change-report compare --repo https://github.com/user/repo v1.0.0 v2.0.0
# Generate only HTML and Markdown reports
dependency-change-report auto --html --markdown
# Ignore dev dependencies
dependency-change-report compare v1.0.0 v2.0.0 --ignore-dev
# Save reports to a specific directory
dependency-change-report auto --output-dir ./reportsProgrammatic Usage
You can also use the tool programmatically in your own Node.js projects:
import { analyzeDependencyChanges } from 'dependency-change-report';
import { generateHtmlReport } from 'dependency-change-report/lib/generate-html.mjs';
import { generateTextReport } from 'dependency-change-report/lib/generate-text.mjs';
// Generate a dependency report
const report = await analyzeDependencyChanges(
'[email protected]:user/repo.git',
'v1.0.0',
'v2.0.0'
);
// Generate an HTML report from a JSON report
await generateHtmlReport('./path/to/report.json', './path/to/output.html');
// Generate a text report from a JSON report
await generateTextReport('./path/to/report.json', './path/to/output.txt');Report Structure
The generated JSON report includes:
- Repository information
- Version comparison details
- Lists of added, upgraded, removed, and modified dependencies
- Changelogs with commit history for upgraded dependencies
- Error information for dependencies that couldn't be analyzed
The HTML report provides a user-friendly visualization of this data, including:
- Summary statistics
- Detailed tables of dependency changes
- Commit history for upgraded dependencies
- Error information
How It Works
- Clones the repository at both the older and newer versions
- Installs dependencies for both versions
- Compares the dependency trees to identify changes
- For each upgraded dependency, clones its repository and analyzes commit history
- Generates a JSON report with all the collected information
- Optionally converts the JSON report to an HTML report
Ignoring Dependencies
Using .dcrignore
You can exclude specific dependencies from your reports by creating a .dcrignore file in your repository root. This is useful for:
- Excluding internal or proprietary packages
- Filtering out dependencies that don't need tracking
- Reducing report noise by ignoring specific packages
Format
The .dcrignore file uses a simple format:
- One package name or pattern per line
- Comments start with
# - Empty lines are ignored
- Whitespace is automatically trimmed
- Supports glob patterns using
*,?,[...]syntax
Example
# Ignore test utilities (exact matches)
jest
mocha
# Ignore all @jest packages (glob pattern)
@jest/*
# Ignore all @types packages (glob pattern)
@types/*
# Ignore build tools
webpack
rollup
esbuild
# Internal packages with wildcard
@mycompany/*
# Complex patterns
babel-plugin-*
*-loaderGlob Pattern Support
The .dcrignore file supports standard glob patterns:
*- Matches any number of characters (except/)?- Matches a single character[abc]- Matches any character in the set[a-z]- Matches any character in the range
Examples:
@types/*- Matches all packages in the @types namespacebabel-*- Matches all packages starting with "babel-"*-loader- Matches all packages ending with "-loader"@mycompany/*- Matches all packages in the @mycompany namespace
Behavior
When a package is listed in .dcrignore:
- It will be excluded from the report along with all its nested dependencies
- Works the same way as
--ignore-devflag - Applies to both direct and transitive dependencies
- The ignored packages are listed in the JSON report under
ignoredFromDcrIgnore
Using with --ignore-dev
The .dcrignore file works in combination with the --ignore-dev flag:
# Ignore both dev dependencies AND packages in .dcrignore
dependency-change-report auto --ignore-devBoth lists are merged together, so packages will be excluded if they appear in either:
- The
devDependenciessection ofpackage.json - The
.dcrignorefile
Configuration (.dcr.json)
A .dcr.json file lets a repo carry its own defaults so local runs and CI behave identically. Every field is optional, and CLI flags always take precedence over config. Commit it to the repo root.
{
"baseline": "v4.2.0",
"ignore": ["@types/*", "eslint*", "jest"],
"ignoreDev": true,
"skipFullInventory": false,
"output": { "dir": "./dcr-reports", "formats": ["html", "markdown"] }
}| Field | Effect |
|---|---|
| baseline | Pins the "older" ref (e.g. a release train). Overrides auto-detection; delete to return to latest-stable-tag detection. |
| ignore | Glob/exact package names to exclude. Merged (union) with any .dcrignore file — both keep working. |
| ignoreDev | Default for --ignore-dev. |
| skipFullInventory | Default for --skip-full-inventory. |
| output.dir / output.formats | Default output directory and which formats (html/markdown/text) to emit. |
Precedence for every setting: CLI flag > .dcr.json > built-in default (except ignore, which is additive). See examples/.dcr.json.
Local multi-repo compare (projects)
For the common "two frontends per product" pattern (e.g. an Electron app and a React Native app sharing private packages), you can generate both reports and compare them locally, in one command — using your own git credentials and ~/.npmrc, with no CI tokens required.
Create a .dcr.json in a coordinating repo (see examples/compare-repo.dcr.json):
{
"projects": [
{ "name": "electron", "path": "../electron-app" },
{ "name": "react-native", "repo": "https://github.com/acme/react-native.git", "ref": "main" }
],
"compare": { "filter": ["react-native*", "@expo/*"], "ignoreDev": true }
}Then run:
dependency-change-report projectsFor each project it: resolves a directory (uses the local path if it's a checkout, otherwise clones repo into a working dir), reads that project's own .dcr.json for baseline/ignore, generates its report, and finally compares the projects (pairwise for two; first-vs-rest for more). Outputs compare-<a>-vs-<b>.json/.md to --output-dir (default: current directory).
Projects are generated in parallel by default (up to 4 at once; --concurrency 1 for serial with progress bars). projects mode also ignores devDependencies by default — a repo can opt back in with "ignoreDev": false in its own .dcr.json.
Work happens in a disk-backed cache dir (~/.cache/dependency-change-report-nodejs), not the system temp dir, because each project installs node_modules for two versions. To keep peak disk small, projects mode installs one version at a time, deletes the older version's node_modules once its metadata is read, and prunes the newer version's node_modules down to just package.json files (all the tool needs afterward). Each project's scratch dir is then deleted right after its report.json is saved to --output-dir. Override the location with --working-dir <path>.
Project fields: name (required), path (local checkout), repo (git URL, cloned if no usable path), ref/baseline (optional per-project overrides). The compare block maps to the dependency-change-compare options (filter→exclude, only→include, ignoreDev, includeNested).
Requirements
- Node.js 18 or higher
- Git
- npm
GitHub Actions Integration
Two reusable composite actions ship from this repo:
| Action | Use |
|---|---|
| ryanramage/dependency-change-report@v1 | Per-repo: generate a report against the last release line, with private npm auth, and publish report.json to a central reports repo. |
| ryanramage/dependency-change-report/compare-action@v1 | Cross-repo: compare two repos' published reports (e.g. Electron vs React Native) to surface dependency drift. |
Copy-paste workflows live in examples/. The two-frontend pattern below (one Electron repo + one React Native repo per product, both consuming private @company/* packages) is the primary use case.
Versioning: the action is versioned independently of the npm package via git tags on this repo — reference
@v1(a moving major tag) to run the1.xaction line. By default the action runs the CLI bundled at that git ref, so it needs nothing published to npm. Set thecli-versioninput only if you want the action to run a specific published npm version vianpxinstead.
Setup (one-time, per org)
Central reports repo. Create a private repo, e.g.
acme/dcr-reports. Each build commits itsreport.jsonhere at a deterministic path so the sibling repo can read it:<product>/<repo-kind>/<event>/<ref-or-pr>/report.json acme/electron/pr/123/report.json acme/react-native/branch/main/report.json acme/electron/tag/v4.3.0/report.jsonTokens (org secrets). The default
${{ github.token }}is repo-scoped and generally cannot read packages published from other repos or write to the reports repo. You need:DCR_PACKAGES_TOKEN—packages:read(to install private@company/*deps).DCR_REPORTS_TOKEN—contents:writeon the central reports repo.
See Token setup & org settings for how to provision these with least privilege.
Token setup & org settings
The two secrets have very different blast radius — handle them separately rather than minting one broad token for both.
| Secret | Need | Can the built-in GITHUB_TOKEN do it? |
|---|---|---|
| DCR_PACKAGES_TOKEN | Read private @company/* from GitHub Packages | Sometimes — if packages + consuming repos share an org |
| DCR_REPORTS_TOKEN | Write report.json to another repo | No — the job token is scoped to its own repo |
Packages: prefer no standing token
If the packages and the frontend repos live in the same org, avoid a PAT entirely:
- Org → Packages → the package → Package settings → Manage Actions access → add each consuming repo with Read.
- In the workflow request the scope and pass the built-in token:
permissions: contents: read packages: read # github-packages-token: ${{ secrets.GITHUB_TOKEN }} # the action default
This token is minted per-run, expires with the job, and is scoped to that repo — nothing to leak or rotate. Use a PAT/App token only if the packages live in another org.
Reports write: GitHub App (recommended) or fine-grained PAT
The cross-repo write always needs a real token. In order of preference:
GitHub App — not tied to a person, mints short-lived (~1h) tokens, precisely scoped, auditable:
- Org → Settings → Developer settings → GitHub Apps → New GitHub App.
- Repository permissions:
Contents: Read and write(reports repo),Packages: Read(only if also using it for packages). Disable the webhook. - Generate a private key; install the app on Only select repositories → the reports repo.
- Store
APP_IDand the private key as org secrets, then mint a token per run:- uses: actions/create-github-app-token@v2 id: app-token with: app-id: ${{ secrets.DCR_APP_ID }} private-key: ${{ secrets.DCR_APP_PRIVATE_KEY }} owner: acme repositories: dcr-reports # reports-token: ${{ steps.app-token.outputs.token }}
Fine-grained PAT — simpler, still scoped, but tied to a user account and manually rotated:
- Org → Settings → Personal access tokens → Settings → enable Allow access via fine-grained personal access tokens (optionally require admin approval). Without this, the token can't touch org resources.
- Create the token with Resource owner = the org, Only select repositories (the reports repo), permissions
Contents: Read and write,Packages: Read. Store as an org secret.
Avoid classic PATs — the repo scope grants write to every repo the user can access, not just the reports repo.
Org settings that matter regardless
- Scope the org secret to selected repos. Org → Settings → Secrets and variables → Actions → New organization secret → Repository access: Selected repositories (the two frontend repos + the coordinating repo). The single most important control — a secret exposed to all repos can be used by any workflow in the org.
- Least privilege. Grant
contents:writeon the one reports repo, not org-wide; install the App on selected repos only. - Rotation & audit. App tokens auto-expire; PATs need a rotation reminder. App activity shows as the app in the audit log; PAT activity shows as the user.
- Fork PRs. Secrets are not passed to workflows triggered by fork
pull_requestevents (default) — relevant only if a frontend repo becomes public.
Per-repo report workflow
Drop examples/dependency-report.yml into each frontend repo as .github/workflows/dependency-report.yml, changing only repo-kind (electron | react-native):
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history — version detection lists tags
fetch-tags: true
- uses: ryanramage/dependency-change-report@v1
with:
product: acme
repo-kind: electron
github-packages-scopes: '@company,@company-internal'
github-packages-token: ${{ secrets.DCR_PACKAGES_TOKEN }}
reports-repo: acme/dcr-reports
reports-token: ${{ secrets.DCR_REPORTS_TOKEN }}How private npm auth works (the part that used to fail)
The tool builds each version in a git worktree of your checkout, and runs npm install there. A project-level .npmrc written at runtime is not visible inside worktrees (they only contain committed files) — which is why ad-hoc .npmrc setups never worked. The action solves this by writing a user-level ~/.npmrc that maps your scopes to GitHub Packages and is visible to every worktree:
@company:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=<DCR_PACKAGES_TOKEN>Set github-packages-scopes to the scopes you consume; the token is passed via env only (never logged). Git auth for the private repo itself continues to work via the runner's authenticated checkout.
Report action inputs (selected)
| Input | Default | Purpose |
|---|---|---|
| base-ref | '' | Explicit baseline; overrides .dcr.json and auto-detection. |
| config-file | .dcr.json | Config file holding a pinned baseline. |
| github-packages-scopes | '' | Comma-separated scopes mapped to GitHub Packages. |
| github-packages-token | ${{ github.token }} | Token for npm.pkg.github.com. |
| publish-target | central-repo | central-repo | artifact | none. |
| reports-repo / reports-token | '' | Central reports repo and its contents:write token. |
| product / repo-kind | '' | Identity used to build the report path. |
| comment-on-pr | true | Post/update the markdown report as a PR comment. |
| fail-on | none | none | major | any — fail the job on changes. |
Outputs: has-changes, added-count, upgraded-count, removed-count, older-version, newer-version, report-json-path, report-path (committed location).
Baseline anchoring (comparing to the last release line)
Baseline ("older" ref) resolves with this precedence:
base-refaction input (per-run override).baselinefield in.dcr.jsonat the repo root.- Auto-detection — the latest stable tag (pre-releases like
-rc/-betaare skipped), falling back tomain/master.
Pin a release train by committing a one-line .dcr.json (see examples/.dcr.json):
{ "baseline": "v4.2.0" }Recommended policy:
- Tag builds: leave it on auto — it compares the released tag against the prior stable tag.
- PR / release-branch builds: pin the baseline via
.dcr.jsonfor the duration of the train (e.g. two weeks). This avoids the footgun where, oncev4.3.0is tagged, auto-detection would comparev4.3.0 → v4.3.0(an empty report). Bump or remove the pin when the train ships.
The pin also works locally:
dependency-change-report auto --base-ref v4.2.0Cross-repo dependency drift
Near sprint end, compare the two repos' latest reports. Put examples/cross-repo-compare.yml in a small coordinating repo (or the reports repo itself):
- uses: ryanramage/dependency-change-report/compare-action@v1
with:
reports-repo: acme/dcr-reports
reports-token: ${{ secrets.DCR_REPORTS_TOKEN }}
report1-path: acme/electron/branch/main/report.json
report2-path: acme/react-native/branch/main/report.json
filter: 'react-native*,@expo/*,electron,electron-*'
comment-issue: '42' # optional: post the drift summary to an issue
# fail-on-discrepancies: 'true'The compare action checks out the reports repo and runs dependency-change-compare against the two local files — so private reports need no special fetch handling. (If you instead point dependency-change-compare at a private URL directly, set DCR_TOKEN so loadReport can send an auth header.)
Adopting on another product
A new team following the same two-repo pattern only changes product, repo-kind, and the secret names — everything else is identical.
License
ISC
