ergebnis/composer-normalize
A Composer plugin that normalizes composer.json, sorting and formatting it consistently so contributors don't argue over style. Install it as a dev dependency, allow the plugin, and run it locally or in CI to keep JSON clean and reduce merge conflicts.
Architecture fit
ergebnis/composer-normalize is a Composer plugin, not a Laravel package. It ships no service provider, no config publish, no facades, and no autoload surface — it registers a NormalizePlugin with Composer's plugin API and exposes a single composer normalize command. For a Laravel codebase this places it squarely in the developer toolchain layer alongside Pint, PHPStan, and php-cs-fixer, with zero production runtime footprint. That is its main architectural virtue: adopting it changes nothing about how the application boots or resolves dependencies, only the byte-level formatting of composer.json.
It is framework-agnostic and applies uniformly to Laravel applications, first-party packages, and split/monorepo sub-packages — each with its own composer.json. There is no Laravel-specific behavior to integrate against, which also means there is no Laravel-specific value beyond formatting hygiene.
Integration feasibility
Low effort, but two non-obvious gates:
allow-plugins consent. Composer 2.2+ requires explicit opt-in (composer config allow-plugins.ergebnis/composer-normalize true). In non-interactive CI this must be pre-seeded in the repo, or installs prompt/fail. This is a per-repo change, not a one-time org setting.extra.composer-normalize in composer.json or via --indent-size/--indent-style, and the extra block always overrides CLI flags. Teams expecting CI flags to win will be surprised. Governance implication: formatting policy lives in the repo, not in the pipeline definition.A PHAR/Phive install path exists as an escape hatch where plugin allowlisting is disallowed or undesirable.
Technical risk
Overall low, with three items worth naming:
composer.lock is current and updates the content-hash after rewriting. In repos where the lock is frequently stale or gitignored, this can cause confusing failures; --no-check-lock / --no-update-lock mitigate but weaken the guarantee.allow-plugins is the control point — this needs a deliberate allowlist decision and a version pin, not an incidental require --dev.Key questions
extra subtree (including extra.laravel.providers and extra.branch-alias) without altering values?composer.json (release scripts, CI matrices, version bumpers)?Dependents: 0 is implausible for a 1,128-star package and likely a Packagist metric gap; the 2026-10-29 last-release date is in the future relative to normal reporting. Both should be re-verified against Packagist before any dependency-policy sign-off.Stack fit
| Layer | Fit | Notes |
|---|---|---|
| Application runtime | None | No service provider, no autoload, no config |
| Composer resolution | Passive | Reads/writes composer.json, updates lock hash |
| Dev toolchain | Strong | Sits beside Pint, PHPStan, php-cs-fixer, GrumPHP/CaptainHook |
| CI | Strong | --dry-run returns exit 1 on drift — a clean gate |
| Scaffolding | Strong | One line in new-project skeletons prevents drift at source |
Migration path
composer.json — root, packages/*, workbench, any split-repo definitions.composer normalize --dry-run --diff per repo to size the change; sanity-check that no semantic deltas appear (only ordering/indentation)..git-blame-ignore-revs if the org uses it.extra.composer-normalize (typical Laravel convention: indent-size: 4, indent-style: space) so local and CI agree.composer normalize --dry-run into CI, advisory first, then required.Compatibility
Composer 2.x is the realistic floor (the allow-plugins flow it documents is 2.2+). Confirm coexistence with other Composer plugins already in the fleet — composer-unused, composer-require-checker, symfony/flex-style plugins — since plugin load order and command namespacing can interact. Run the pilot with the full plugin set present, not a stripped repo.
Sequencing
Normalize before any large dependency-refresh initiative, never during one. Doing both in the same window turns a clean ordering diff into an unreviewable mixed change and makes the lock hash churn impossible to attribute. Ideal order: adopt formatter → normalize all repos → freeze → then proceed with version upgrades.
Maintenance
Dev-only scope means no CVE-response obligation on production images — a broken release degrades a formatting check, not the application. The real maintenance cost is policy maintenance: the allow-plugins allowlist, the pinned version, and the extra indentation convention must stay consistent across every repo. Drift here is silent — CI passes, formatting differs.
Support
Documentation is above average for the ecosystem: README covers install (Composer/PHAR/Phive), all flags, and CI usage; changelog and releases are maintained. Exit-code semantics are documented, which matters more than prose quality for a CI gate. Fallback paths (PHAR, Phive) mean the team is never fully blocked by a plugin-policy decision.
Scaling
Negligible. Cost is O(1) per composer.json — milliseconds. The only scaling concern is breadth: a monorepo with N packages needs N invocations (or a looped CI step), since the file argument targets one path at a time. Budget a small scripting wrapper, and expect CI wall-clock impact to be immeasurable.
Failure modes
| Failure | Symptom | Mitigation |
|---|---|---|
| Plugin not allowlisted | command not found / install prompt hangs in CI |
Pre-seed allow-plugins in repo composer.json |
| Unnormalized file | CI exit 1 with diff |
Expected; fix locally or via auto-run |
| Stale/missing lock | Command refuses or hash updates unexpectedly | --no-check-lock / --no-update-lock, or fix lock hygiene |
extra vs CLI conflict |
--indent-size silently ignored |
Document that extra wins; avoid passing flags |
| First-run mega-diff | Merge conflicts across active branches | Single-purpose commit, coordinated window |
| Composer major bump | Plugin API break | Pin minor; test in pilot repo first |
Ramp-up
Effectively zero for individual engineers — one command, and the output is self-explanatory. The actual effort is organizational, not technical: ~1 day to inventory repos, decide the allowlist and indentation convention, pilot, and land the CI gate. Plan the rollout as a governance task owned by platform/DevEx, not a feature task assigned to an application team — the value only materializes when every repo enforces the same rule.
How can I help you explore Laravel packages today?