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

Monorepo Builder Laravel Package

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+.

View on GitHub
Deep Wiki
Context7

Getting Started

monorepo-builder is a dev-only Composer CLI that keeps a PHP monorepo's composer.json files consistent and automates releases. It is not a runtime library.

Install (PHP 8.2+; on PHP 8.1 pin symplify/monorepo-builder:^11.2, which is unmaintained):

composer require monorepo-php/monorepo --dev

First three commands to learn:

vendor/bin/monorepo-builder init      # one-time: scaffolds packages/ + monorepo-builder.php
vendor/bin/monorepo-builder merge     # folds every package composer.json into root
vendor/bin/monorepo-builder release v1.0 --dry-run

Where to look first: the root monorepo-builder.php. Everything is configured through a closure receiving MBConfig:

use Symplify\MonorepoBuilder\Config\MBConfig;

return static function (MBConfig $mbConfig): void {
    $mbConfig->packageDirectories([__DIR__ . '/packages']);
};

First real use case: you already have packages/*/composer.json files with their own require, autoload, and require-dev. Run init (or hand-write the config), then merge, commit the regenerated root composer.json, and run composer update once at the root. From then on, per-package dependency edits are followed by a merge instead of manual root edits.

Implementation Patterns

Merge is the daily driver. Run vendor/bin/monorepo-builder merge whenever a package's composer.json changes. It merges all sections — standard (require, autoload) and custom (scripts-aliases, abandoned, …). Key behaviors to rely on:

  • An entry in both require and require-dev resolves to the require version.
  • Existing root key order is preserved; new sections append at the end.
  • Root autoload aggregates every internal package's PSR-4 so root-level vendor/bin/phpunit resolves all namespaces.

Enforce dependency consistency in CI. validate fails when packages disagree on a shared dependency version. Pair it with merge in a "no drift" job: run merge and git diff --exit-code composer.json to catch a package edit that was never folded in.

Version bumps across packages. After releasing, bump-interdependency "^4.0" rewrites mutual package dependencies to a version constraint. propagate goes the other direction, pushing root composer.json versions back into each package — useful when the root is the source of truth.

Pre-split smoke test. localize-composer-paths points mutual package requirements at local paths, letting you test the monorepo as if the packages were installed from Packagist. package-alias syncs each package's branch-alias to the current version (format configurable).

Release pipeline as a first-class workflow. Default pipeline is TagVersionReleaseWorker → PushTagReleaseWorker. Typical richer pipeline configured in monorepo-builder.php:

$mbConfig->workers([
    UpdateReplaceReleaseWorker::class,
    SetCurrentMutualDependenciesReleaseWorker::class,
    AddTagToChangelogReleaseWorker::class,
    TagVersionReleaseWorker::class,
    PushTagReleaseWorker::class,
    SetNextMutualDependenciesReleaseWorker::class,
    UpdateBranchAliasReleaseWorker::class,
    PushNextDevReleaseWorker::class,
]);

Use disableDefaultWorkers() to start from scratch, or implement ReleaseWorkerInterface for custom steps. Always dry-run first: release v7.0 --dry-run. Semver shortcuts work too — release patch turns v0.7.1 into v0.7.2.

Tame merge output rather than post-editing the root file. Use dataToAppend() / dataToRemove() to inject shared dev tooling or strip sections, and composerSectionOrder(JsonSchema::getProperties()) to impose a stable layout:

$mbConfig->dataToAppend([
    ComposerJsonSection::REQUIRE_DEV => ['phpstan/phpstan' => '^2.1'],
]);
$mbConfig->dataToRemove([
    ComposerJsonSection::REPOSITORIES => [Option::REMOVE_COMPLETELY],
]);

Mixed monorepo of libraries + apps. If apps consume libraries via path repositories and rely on Composer symlinks, disable replace and scope the autoload skip:

$mbConfig->disablePackageReplace();
$mbConfig->disableAutoloadMerge(
    sections: [AutoloadSection::Autoload],
    forTypes: [PackageType::Library],
);

Libraries get symlinked and registered by Composer; only apps' PSR-4 lands in the root autoload.

Custom package roots. packageDirectories([...]) accepts several roots; packageDirectoriesExcludes([...]) carves out internal packages that shouldn't participate.

Gotchas and Tips

  • merge owns the root composer.json. Hand edits to sections that merge produces will be overwritten on the next run. Put persistent additions in dataToAppend() and removals in dataToRemove().
  • autoload-dev is root-only in Composer. Path-repo dependencies' dev autoload is never registered in the consumer's vendor/composer/autoload_psr4.php. If root CI runs vendor/bin/phpunit and expects library test classes, you must keep folding AutoloadSection::AutoloadDev — skipping it breaks cross-package test discovery.
  • forTypes does not use Composer's "missing type means library" default. A package with no explicit type is not matched by forTypes: [PackageType::Library]. Declare type: library explicitly in the package's composer.json if you want the filter to see it.
  • disableAutoloadMerge() is now argument-based. The old zero-arg call still works but emits E_USER_DEPRECATED and maps to full-kill behavior. Repeated calls compose across different sections but are last-call-wins per section. Prefer shouldSkipAutoload($type) / shouldSkipAutoloadDev($type) over the deprecated isAutoloadMergeDisabled() getter.
  • disablePackageReplace() and autoload skipping are a package deal. Turning off replace without skipping library autoload merging duplicates registration when Composer already symlinks those libraries into vendor/.
  • Escape hatch for ecosystem types. forTypes accepts strings as well as enum cases — 'wordpress-plugin', 'drupal-module', 'symfony-bundle', or custom types. Enum cases and strings can be mixed; multiple types are OR-matched.
  • Multiple release lines need branch-aware tag validation. By default release compares against the latest tag globally, so backporting an LTS patch may be rejected. Enable the branch-aware option in config to compare only within the same major.
  • release mutates and pushes. It bumps interdependencies, tags, and pushes. Run --dry-run on a clean working tree and read the plan; there's no undo beyond git reset plus deleting the remote tag.
  • init never overwrites. Safe to re-run, but it won't repair an existing monorepo-builder.php — edit it directly.
  • Debugging merge surprises: diff the root composer.json after merge (git diff composer.json) and check the package-level composer.json that introduced the change. Root key order being preserved means appended sections appear at the bottom — that's expected, not a bug.
  • Config must return a closure. A monorepo-builder.php that returns an array or executes configuration at include time silently does nothing.
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.
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules
loco/loco
starfederation/datastar-php
yupmin/modern-phystrix
stephenjude/extended-artisan-commands
lumphp/laravel-macroable
lumphp/laravel-collections
lumphp/laravel-contracts
sermepa/sermepa
arkecosystem/fortify