Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Composer Lock Diff Laravel Package

davidrjonas/composer-lock-diff

A CLI tool that compares two composer.lock files and reports which packages were added, removed, or updated, including version changes. Useful for changelogs, release notes, and reviewing dependency upgrades in pull requests. Outputs plain text or Markdown tables.

Deep Wiki
Context7

Getting Started

Add the tool as a dev dependency so it never ships to production:

composer require --dev davidrjonas/composer-lock-diff

It exposes an executable at vendor/bin/composer-lock-diff (and, when the plugin is enabled, a composer lock-diff subcommand). The mental model is simple: point it at two composer.lock files — either on disk or as git refs — and it prints a table of every package that was added, removed, upgraded, or downgraded.

First use case, and the one you will reach for constantly: reviewing a composer update before you commit it.

cp composer.lock /tmp/composer.lock.before
composer update
vendor/bin/composer-lock-diff --from=/tmp/composer.lock.before --to=composer.lock

Look at the output first if you are just exploring: it defaults to a plain table, and --pretty gives you color and unicode borders for readability. The --from/--to pair is the only interface you need to learn — everything else is plumbing around those two inputs.

For reviewing someone else's branch (or a Dependabot/Renovate PR) without checking it out:

git fetch origin
vendor/bin/composer-lock-diff --from=origin/main --to=origin/dependabot/composer/laravel/framework-10.x

That is the "where to look first" — refs work just as well as paths, and git resolves them for you.

Implementation Patterns

PR review summaries. The highest-leverage integration is a GitHub Actions job that posts (or edits) a PR comment with the package delta whenever composer.lock changes. Run the diff against the merge base, capture stdout, and use gh pr comment or actions/github-script to upsert it. This turns a 2,000-line lock file diff into ten readable rows, which is what reviewers actually need to see.

- name: Composer lock diff
  if: contains(github.event.pull_request.changed_files, 'composer.lock')
  run: |
    git fetch origin ${{ github.base_ref }} --depth=1
    vendor/bin/composer-lock-diff \
      --from=origin/${{ github.base_ref }} --to=HEAD \
      --no-dev --pretty > lock-diff.txt

CI gating on unexpected changes. If your policy is "no transitive upgrades outside Dependabot PRs", run the diff on every PR and fail the build when it emits rows (a small wc -l or exit-code check around it). Pair this with --no-dev in production-focused pipelines so phpunit, pest, and larastan bumps do not trip the gate.

Local pre-push / audit workflow. Drop it in a Composer script so it becomes muscle memory:

"scripts": {
  "lock:diff": "composer-lock-diff --from=HEAD --to=composer.lock --pretty",
  "update:safe": [
    "cp composer.lock /tmp/composer.lock.bak",
    "composer update --prefer-dist",
    "composer lock:diff --from=/tmp/composer.lock.bak --to=composer.lock --pretty"
  ]
}

Run composer update:safe and you immediately see the blast radius before you stage anything.

Wrapping in an Artisan command. If your team has an internal php artisan deps:diff command, wrap the binary with Symfony\Component\Process\Process rather than re-implementing lock parsing:

$process = new Process([
    base_path('vendor/bin/composer-lock-diff'),
    '--from=HEAD~1',
    '--to=composer.lock',
    '--no-dev',
]);
$process->mustRun();
$this->line($process->getOutput());

Keep --no-dev and formatting flags as command options so the same wrapper serves both the audit and CI use cases.

Release notes. Before tagging a minor release, diff the previous release tag against HEAD (--from=v1.4.0 --to=HEAD) and paste the output into the changelog's "Dependencies" section. It is far more accurate than eyeballing composer outdated.

Scoping noisy output. Use --only-name when you only need to know what moved, and --only-version when you are building a compact changelog table. Combine with --no-dev in production-audit contexts and omit it when you are chasing a test-only regression.

Gotchas and Tips

  • It reads lock files, not composer.json. Both --from and --to must resolve to an actual composer.lock. Comparing composer.json or a constraint string will not do what you want. If you need to compare "what a fresh update would do", copy the current lock to a temp path first (as above) rather than trying to diff a manifest.
  • Refs must exist locally. In shallow CI checkouts, origin/main may not be fetched and the tool will error out unhelpfully. Always git fetch the base ref with sufficient depth before invoking it — this is the single most common CI failure with this tool.
  • Dev dependencies are noisy. Laravel projects are notorious for large dev trees (Pest, PHPStan, Rector, Pest plugins). Default to --no-dev in any automated output that a reviewer will read, and only drop the flag when auditing vulnerabilities or test breakage.
  • Composer 1 vs Composer 2 lock formats. If your project pins an old Composer version or you are diffing across a runtime upgrade, confirm both lock files were written by the same major Composer release; the package structure around plugins and platform requirements changed substantially in 2.0 and can confuse a naive read.
  • Do not install it in production. It is a --dev dependency. If your deploy composer install --no-dev step runs in the same image, the binary simply will not be there — guard any Artisan command that shells out to it with a file_exists(base_path('vendor/bin/composer-lock-diff')) check.
  • Output is for humans by default. For machine consumption, strip the table borders (a |-split or awk is usually enough) or check whether your version exposes a JSON flag — but for a PR comment the pretty table is exactly right, so resist over-engineering this.
  • Exit codes reflect failure, not "there were changes." A zero exit does not mean "nothing changed." If you are gating on changes, parse the output or compare lock file hashes; do not rely on the process status.
  • It is a Composer plugin. If your project disables plugins ("config": { "allow-plugins": false } or a strict allow-plugins allowlist in Composer 2.2+), you must explicitly allow davidrjonas/composer-lock-diff. Otherwise the composer lock-diff subcommand silently will not register, even though the vendor/bin script works. Prefer the vendor/bin binary in scripts for exactly this reason.
  • Extension point is the shell. There is no PHP API or Laravel facade worth depending on. Treat it as a well-behaved Unix tool: feed it two paths or refs, capture stdout, render it your way. That keeps you insulated from internal changes across releases and makes it trivially testable.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle