feat: add deploy script and update composer dependencies for private setup#83
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a Deployer-based deployment entrypoint and updates Composer configuration/dependencies to support installing Yard/private packages (via satis/satispress) and deploying “brave” and “gemeente” accept demo sites.
Changes:
- Added a root-level
deploy.phpconfigured for Yard wp-deployer, with two WPACC01 accept hosts and post-vendors hooks. - Updated
composer.jsonto include satis/satispress repositories, switch toyard/wp-cli-bundle, add helper scripts, and addyard/wp-deployer. - Refreshed
composer.lockaccordingly (new/updated dependencies including Deployer + Yard packages).
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| deploy.php | Introduces Deployer config (repo + hosts) and post-install provisioning steps. |
| composer.json | Adds private Composer repositories and scripts for installing deployer/private packages. |
| composer.lock | Locks updated dependency graph including Yard wp-deployer and related packages. |
0235c2f to
5b58ef2
Compare
|
@claude review |
5b58ef2 to
ad5d59f
Compare
| host(Host::WPACC01) | ||
| ->setDeployPath('/data/www/accept-sites/yard/brave') | ||
| ->setLabels(['stage' => Stage::ACCEPT, 'site' => 'brave']); | ||
|
|
||
| host(Host::WPACC01) | ||
| ->setDeployPath('/data/www/accept-sites/yard/gemeente') | ||
| ->setLabels(['stage' => Stage::ACCEPT, 'site' => 'gemeente']); |
There was a problem hiding this comment.
🔴 Both host entries in deploy.php (lines 44–50) call host(Host::WPACC01) with the same constant, so in Deployer 7.x the second call retrieves and mutates the same Host object, overwriting the brave deploy path and labels with gemeente config. After boot, only one host exists with site=gemeente; running dep deploy site=brave finds zero matching hosts and fails entirely. Fix: use distinct aliases — host('brave')->setHostname(Host::WPACC01) and host('gemeente')->setHostname(Host::WPACC01).
Extended reasoning...
What the bug is
In Deployer 7.x, the host() function uses its first argument as the host alias (the unique key in the hosts collection). When called a second time with an alias that already exists, the function explicitly returns the existing Host object rather than creating a new one. The relevant logic from Deployer's src/functions.php is:
if (count($hostname) === 1 && $deployer->hosts->has($hostname[0])) {
return $deployer->hosts->get($hostname[0]);
}The specific code path
Lines 44–50 of the newly added deploy.php:
host(Host::WPACC01)
->setDeployPath('/data/www/accept-sites/yard/brave')
->setLabels(['stage' => Stage::ACCEPT, 'site' => 'brave']);
host(Host::WPACC01)
->setDeployPath('/data/www/accept-sites/yard/gemeente')
->setLabels(['stage' => Stage::ACCEPT, 'site' => 'gemeente']);Host::WPACC01 is the same constant string both times (e.g., 'wpacc01.yard.nl'). The second call to host(Host::WPACC01) retrieves the Host object created by the first call and mutates it in place with setDeployPath and setLabels.
Why existing code doesn't prevent it
There is no guard or uniqueness check in the call sites. Both calls look syntactically valid and the developer's intent is clear (two logical sites on one physical server), but the Deployer API requires distinct aliases — not distinct hostnames — to register two separate host entries.
Impact
After both host() calls execute during dep init, Deployer's host registry contains exactly one entry: the brave host object, now carrying gemeente's deploy path (/data/www/accept-sites/yard/gemeente) and label (site=gemeente). The brave configuration is permanently lost in memory. Running dep deploy site=brave selects hosts by label site=brave, finds zero matches, and exits with No hosts selected. The brave deployment is completely broken.
Step-by-step proof
- PHP loads
deploy.php. Line 44 callshost(Host::WPACC01)— a new Host is created with aliaswpacc01.yard.nland added to the hosts collection.setDeployPath('/data/www/accept-sites/yard/brave')andsetLabels(['site' => 'brave'])are applied. - Line 48 calls
host(Host::WPACC01)again. Deployer sees the alias already exists and returns the same Host object. setDeployPath('/data/www/accept-sites/yard/gemeente')overwrites the path on the existing object.setLabels(['site' => 'gemeente'])overwrites the labels on the existing object.- Host registry now: one entry — alias
wpacc01.yard.nl, deploy pathgemeente, labelsite=gemeente. - Developer runs
dep deploy site=brave. Deployer filters hosts wheresite=brave— zero matches. - Deploy exits:
No hosts selected.
How to fix it
Use distinct aliases and set the physical hostname separately:
host('brave')
->setHostname(Host::WPACC01)
->setDeployPath('/data/www/accept-sites/yard/brave')
->setLabels(['stage' => Stage::ACCEPT, 'site' => 'brave']);
host('gemeente')
->setHostname(Host::WPACC01)
->setDeployPath('/data/www/accept-sites/yard/gemeente')
->setLabels(['stage' => Stage::ACCEPT, 'site' => 'gemeente']);This registers two distinct entries in the hosts collection, both pointing to the same physical server, each with its own deploy path and labels.
| "private-deployer": [ | ||
| "composer require yard/wp-deployer --dev" | ||
| ], |
There was a problem hiding this comment.
🟡 The private-deployer script runs composer require yard/wp-deployer --dev with no version constraint, meaning each developer gets whatever version is latest at install time. If yard/wp-deployer releases a major version (e.g. v2.0) that changes the Host, Loader, Stage, or wp() API used in deploy.php, the deploy tool will silently break for anyone who installs the newer version. Fix by pinning to a semver range, e.g. composer require yard/wp-deployer:^1.5 --dev.
Extended reasoning...
What the bug is
The private-deployer Composer script (composer.json lines 121–123) runs:
"composer require yard/wp-deployer --dev"
No version constraint is specified. While Composer will add a caret constraint based on the currently-available major version when resolving, this means the constraint written into composer.json and composer.lock depends entirely on what version is published at the moment each developer runs the script. Developer A running it today gets ^1.5, developer B running it after a future major release gets ^2.0 — with incompatible API.
The specific code path
deploy.php directly imports and uses the Yard Deployer API:
use Yard\Deployer\Host;
use Yard\Deployer\Loader;
use Yard\Deployer\Stage;
use function Yard\Deployer\wp;If any of these classes or the wp() function signature changes between major versions, deploy.php will throw a fatal error or silently misbehave on hosts where the newer major version was installed via this unconstrained script.
Why existing code doesn't prevent it
yard/wp-deployer is NOT listed in the require-dev section of composer.json — the private-deployer script is the only installation mechanism. Unlike private-packages and gemeente-packages which run with --no-interaction, this script has no version guard at all. The previous iteration of the code pinned yard/wp-deployer to a feature branch (dev-feat/migrate-to-localhost), which Copilot flagged as brittle. This PR replaced the branch pin with no constraint at all — arguably a step backwards in reproducibility.
Impact
This is a developer setup script, not part of the automated CI/CD pipeline, so it won't cause immediate failures. However, in a boilerplate/template repo shared across a team, running composer private-deployer at different points in time will produce divergent composer.json and composer.lock states. A future yard/wp-deployer v2.0 release with API changes would silently break deploys for any team member who ran the script after the major release, while others on v1.x continue to work.
Step-by-step proof
- Developer A runs
composer private-deployertoday — getsyard/wp-deployer: ^1.5written to theircomposer.json+composer.lock. yard/wp-deployerv2.0 is published, with a renamedLoaderclass or changedwp()signature.- Developer B runs
composer private-deployernext week — getsyard/wp-deployer: ^2.0written to their files. - Developer B's
dep deployfails with a fatal PHP error becausedeploy.phpstill uses the v1.x API. - Meanwhile Developer A's setup continues to work, making the bug hard to reproduce and diagnose.
How to fix
Pin to the version that is confirmed working (v1.5.3 per composer.lock):
"private-deployer": [
"composer require yard/wp-deployer:^1.5 --dev"
]This allows patch and minor updates (non-breaking per semver) while protecting against a future major-version API break.
deploy.php en private repositories (satis, satispress) toegevoegd aan Brave
wp-deployer installeren:
composer private-deployerprivate packages installeren:
composer private-packagesgemeente packages installeren:
composer gemeente-packagesdeployen:
dep deploy site=brave=> https://brave.wpacc01.yard.nldep deploy site=gemeente=> https://gemeente.wpacc01.yard.nl/