Until now a release could only come from the bare repo on the VPS, so code
that never reached this server's git remote could not be deployed at all.
exo-deploy now also takes a zip of the landing source directories:
exo-deploy --zip /tmp/exo-20260101.zip medcenterphysio
exo-deploy --zip /tmp/exo-20260101.zip all
Everything after the source lands in the release directory is unchanged and
shared by both modes — npm ci, the build, the %VITE_SITE_URL% check, prune,
gzip, the atomic swap, the health check with rollback, the KEEP=3 rotation.
The archive therefore carries sources, never a build: VITE_SITE_URL keeps
coming from /etc/exo/<app>.build.env rather than from whoever packed the zip.
Any .env that travelled inside the archive is dropped before the build.
The archive is unpacked once, node_modules/dist/__MACOSX/.DS_Store skipped,
and every target app is validated up front — a missing directory or a missing
package-lock.json is reported for all four at once, before anything on disk
moves. The landing directory is located by its package.json, so both
fitnes/... and bundle/fitnes/... (what Finder's Compress produces) work.
Also new, and shared by both modes: `all` deploys the four in apps.conf order,
stopping at the first failure, and each release records where it came from in
.deploy-source, so a live release can still be traced once the deploy output
is gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>