Skip to content

fix: ensure first composer install symlinks local packages#663

Merged
dgafka merged 3 commits into
mainfrom
ensure-easy-local-setup
May 7, 2026
Merged

fix: ensure first composer install symlinks local packages#663
dgafka merged 3 commits into
mainfrom
ensure-easy-local-setup

Conversation

@dgafka
Copy link
Copy Markdown
Member

@dgafka dgafka commented May 6, 2026

Why is this change proposed?

Cloning the repo, cd-ing into a package (e.g. packages/Amqp), and running composer install did not symlink sibling Ecotone packages — it pulled them from Packagist instead. A second composer install was required for wikimedia/composer-merge-plugin to kick in and apply the path repositories from local_packages.json. New contributors (and CI) hit this chicken-and-egg every time, and the workaround leaked into both the GitHub Actions workflows (composer global require wikimedia/composer-merge-plugin) and the docker-compose env (APP_MERGE_PLUGIN: yes). This PR makes the first composer install work correctly with no extra steps.

Description of Changes

  • Replaced extra.merge-plugin indirection in every packages/*/composer.json, _PackageTemplate/composer.json, and quickstart-examples/**/composer.json with a top-level repositories field using a wildcard path (../*, packages/*, etc. depending on depth).
  • Removed wikimedia/composer-merge-plugin from require/require-dev and from config.allow-plugins (no longer needed).
  • Deleted the now-orphaned local-packages.json and packages/local_packages.json.
  • Removed the "Enable merge-plugin" steps from .github/workflows/split-testing.yml and .github/workflows/quickstart-examples.yml.
  • Removed the dead APP_MERGE_PLUGIN env var from docker-compose.yml.

Why it's safe for end users

Composer ignores repositories defined in dependency packages — only the root project's repositories are honored. So when a user runs composer require ecotone/amqp in their own project, the wildcard repositories block inside ecotone/amqp's composer.json is read, then discarded. Sibling deps continue to resolve from Packagist as before.

Verification

I tested both halves of the contract inside the project's docker container.

End-user safety — set up a consumer project that pulled ecotone/amqp via path repo, planted a poison-pill ecotone/enqueue@1.309.999 next to it that would only get installed if Composer honored the dep's repositories. Result: real ecotone/enqueue@1.309.3 was downloaded from Packagist, poison-pill was ignored.

Local dev fix — fresh composer install (no prior vendor/lock) inside multiple packages and quickstart-examples, all symlink siblings on the first try:

$ cd packages/Dbal && composer install
- Installing ecotone/ecotone (dev-main): Symlinking from ../Ecotone
- Installing ecotone/enqueue (dev-main): Symlinking from ../Enqueue
- Installing ecotone/jms-converter (dev-main): Symlinking from ../JmsConverter

$ cd quickstart-examples/MultiTenant/Laravel/EventSourcing && composer install
- Installing ecotone/ecotone (dev-main): Symlinking from ../../../../packages/Ecotone
- Installing ecotone/laravel (dev-main): Symlinking from ../../../../packages/Laravel
...

Full phpunit suite (1239 tests) passes in packages/Ecotone after the refactor.

Use case scenarios

  • New contributor onboarding — clone the repo, cd packages/Dbal, composer install, run tests. No second install, no docs about merge-plugin needed.
  • Adding a new package — drop a directory under packages/ with a composer.json. The wildcard picks it up automatically; no central list to update.
  • Working in a quickstart-examplecd quickstart-examples/Schedule, composer install, run the example. Sibling packages come from local source, so changes in packages/Ecotone are reflected immediately.

Resolution flow

flowchart LR
    A[composer install in packages/Amqp] --> B{repositories field}
    B -->|top-level<br/>read immediately| C[Discover ../* siblings]
    C --> D[Symlink ecotone/enqueue from ../Enqueue]
    style B fill:#cfc
    E[composer install in packages/Amqp<br/>old behavior] --> F{merge-plugin installed?}
    F -->|no, first run| G[Resolve ecotone/enqueue from Packagist]
    F -->|yes, second run| H[Symlink from ../Enqueue]
    style F fill:#fcc
Loading

Pull Request Contribution Terms

  • I have read and agree to the contribution terms outlined in CONTRIBUTING.

dgafka added 3 commits May 6, 2026 20:53
Replace wikimedia/composer-merge-plugin indirection with a top-level
path repository in each composer.json. The merge plugin could only
intercept Composer events after it was already installed, so the first
composer install in a sub-package fetched siblings from Packagist
instead of symlinking from local paths — requiring a second install
for the plugin to kick in.

A top-level repositories field is read by Composer immediately, so
sibling packages resolve to local symlinks on the first install with
no plugin needed. End-user installs are unaffected because Composer
ignores repositories defined in dependency packages.
# Conflicts:
#	packages/Amqp/composer.json
#	packages/DataProtection/composer.json
#	packages/Dbal/composer.json
#	packages/Enqueue/composer.json
#	packages/Kafka/composer.json
#	packages/PdoEventSourcing/composer.json
#	packages/Redis/composer.json
The new top-level repositories wildcard works inside the monorepo, but
points to nonexistent paths once the directory is split into a
standalone repo. For package splits (../*) it falls through silently,
but the quickstart-examples split (../../packages/*) refers to a parent
directory that does not exist next to a clone of the split repo, which
made composer install fail hard with "the url supplied for the path
repository does not exist".

Add a release-time helper that strips type=path entries from every
composer.json under the package directory and run it in the split job
before invoking the split action. Monorepo composer.jsons are
unaffected — the strip only runs on the in-memory checkout used by the
split job.
@dgafka dgafka merged commit 131ef6c into main May 7, 2026
10 of 11 checks passed
@dgafka dgafka deleted the ensure-easy-local-setup branch May 7, 2026 06:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant