The build was gated on `git diff BEFORE AFTER`, where BEFORE was HEAD
before checkout. Re-deploying the same ref (or re-running after an
aborted deploy) made BEFORE == AFTER, so the diff was empty and the SPA
build silently skipped — shipping stale assets while migrations still ran.
- Always rebuild the SPA; only gate the heavy dep installs.
- npm ci / composer install also run when node_modules / vendor are missing.
- Track the last successfully deployed commit in .deploy-last-commit and
diff against that, so an aborted run never advances the baseline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One-command deploy: maintenance mode, checkout ref (branch or tag), conditional
composer install / npm build (only when their inputs changed), migrate, cache
rebuilds, queue restart, back up. Aborts and stays in maintenance mode on
failure. Mirrors docs/ops/deployment.md §8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>