symplify/monorepo-builder
Toolkit for managing PHP monorepos: merge package composer.json files into the root, validate dependency versions, bump interdependencies, propagate versions back, manage package aliases, and automate releases. Configured via monorepo-builder.php; requires PHP 8.2+.
Architecture fit
vendor/bin/monorepo-builder from the repo root against a monorepo-builder.php config file. For a Laravel shop this is a neutral-to-positive on architecture: zero coupling to the framework means the tool survives Laravel major upgrades, but it also means all Laravel-specific wiring (composer scripts, CI jobs, release approvals) is glue we own.packages/* (shared domain libraries, internal SDKs, Blade/UI packages) plus one or more Laravel type: project apps. The commands that matter to us are merge (single root composer.json so root-level PHPUnit/PHPStan/Pint can resolve every namespace), validate (kill shared-dependency version drift across packages), and release (deterministic tag + next-dev bump).Integration feasibility
composer require --dev, and validate is usable immediately in read-only mode with no config beyond package discovery. The complexity is concentrated in two configuration areas that are genuinely subtle:
autoload-dev as root-only; path-repo dependencies never register their dev autoload. So if we skip AutoloadDev folding, root-level PHPUnit silently stops discovering library test classes. This is a config decision with a non-obvious CI failure signature.replace section. Default behavior writes replace entries at self.version for every internal package, which short-circuits Composer's symlink install for path-repo consumers. Laravel apps that require internal libraries via path repos are exactly the case that breaks. disablePackageReplace() + selective disableAutoloadMerge() is the documented escape hatch, but it is a decision a TPM should make before rollout, not during an incident.release) mutates git state — bumps interdependencies, tags, pushes tags, pushes next dev versions. That is a governance decision as much as a technical one; it must be gated behind protected-branch policy and ideally manual approval.^11.2 supports 8.1 but is explicitly unmaintained).Technical risk
composer require monorepo-php/monorepo --dev while the package under evaluation is symplify/monorepo-builder. A rename or namespace migration in flight is a real risk: we could be pinning a package whose canonical name, Packagist vendor ownership, and future release channel are ambiguous. Must be verified before the first install.merge rewrites the root composer.json; release pushes tags. Both are high-consequence if invoked ad hoc by a developer rather than through a pipeline.validate is not a CI gate, version drift across packages returns within weeks and the tool's main benefit evaporates.Key questions
symplify/monorepo-builder or monorepo-php/monorepo — and who controls the Packagist vendor?replace, package-alias, propagate, and localize-composer-paths are in scope at all.path repos + symlinks? If yes, disablePackageReplace() is mandatory from day one.monorepo-builder.php config as a first-class artifact (review, test, document)?scripts + a small custom PHP script, or a fork?Stack fit
composer.json."monorepo:validate", "monorepo:merge") so nobody invokes the binary with ad hoc flags, and so the commands are discoverable and greppable.Migration path
validate only, in report-only mode, for two sprint cycles. It makes no writes and surfaces exactly the drift problem it exists to solve, which gives you a data-backed adoption case.merge on a throwaway branch, with the generated root composer.json diff reviewed in a PR and the pre-existing file kept as the fallback for one release cycle.release in --dry-run inside CI, comparing predicted output against what the release manager actually did manually.Compatibility
config.platform.php) so local and CI behavior match; this also aligns with Laravel 11/12.type: project Laravel app and one type: library package with tests, so both autoload branches and the replace/path-repo interaction are exercised before any commitment.type explicitly in every internal package's composer.json. The type filter in disableAutoloadMerge() matches literally and does not apply Composer's "missing type defaults to library" rule — untyped packages will silently slip through.Sequencing
validate in CI as a non-blocking job; baseline the drift count.replace on/off, autoload skip matrix, package discovery paths, section ordering. Document the decisions and the rationale in the repo.validate to blocking; add merge to a scheduled/PR job with diff review.propagate, package-alias, localize-composer-paths, LTS branch-aware validation.Maintenance
monorepo-builder.php file is a living artifact that must be reviewed whenever a package is added, a type changes, or the autoload strategy shifts. Budget roughly one named owner and a few hours per release cycle.MBConfig API changes — the README already shows one migration (the zero-argument disableAutoloadMerge() deprecation), which is a signal that config APIs are still moving.E_USER_DEPRECATED) should be treated as hard CI failures so config rot surfaces immediately rather than at the next upgrade.Support
merge diff, how to resolve autoload-dev discovery failures, and how to unwind a partial release.Scaling
merge and validate are linear and cheap; CI time grows with the number of packages, not with the tool.ReleaseWorkerInterface) — good sign for long-term fit if our release process becomes more elaborate.Failure modes
release succeeds at TagVersionReleaseWorker and fails at PushNextDevReleaseWorker → tag exists, dev branch doesn't. Recovery: manual next-dev bump. Mitigation: --dry-run in CI plus manual approval, and worker ordering reviewed by the release owner.replace enabled alongside path repos → apps never pick up library changes; local builds pass against cached code. Mitigation: disablePackageReplace() if we use path repos at all.AutoloadDev unless we have an alternative discovery mechanism; if we must, document it prominently.composer.json churn. merge rewrites a file many people edit by hand. Mitigation: treat root composer.json as generated, commit diffs through PR review, and forbid manual edits in the contributing guide.Ramp-up
validate; 1–2 days to confidently own the autoload/replace configuration.replace/autoload configuration as a design decision with a written record rather than a config file someone tweaks. The framework-agnostic, dev-only nature makes the downside bounded; the thin ecosystem and ambiguous package naming make verification the gating step.How can I help you explore Laravel packages today?