PR #246 merged: feat: add wait option to deploy tool (#238)
Summary
Closes #238
deploy used to fire-and-forget: a build could fail (e.g. build container OOM-killed) while the old container kept serving, and the caller never found out — a deployed-looking site that's actually still on stale code.
Adds an opt-in wait: boolean (+ timeout_seconds, default 300) to the deploy tool:
- Triggers the deploy as before, then polls
getDeploymentevery ~5s until a terminal status (finished/failed/cancelled) or the timeout elapses. - Success: returns
status,deployment_uuid,commit, timestamps, andduration_seconds. - Failure: same, plus a bounded log tail (
truncateLogs, last 30 entries / 10KB) — built from essential fields only, never the raw upstreamDeploymentobject (which can carryserver/applicationsecrets — see #232). - Timeout: current status +
timed_out: true+ anext_actionhint to keep polling viadeployment get. - No-wait path is byte-for-byte unchanged.
Multi-deployment tags
A tag can match several applications, so Coolify's /deploy response can contain multiple deployment_uuids. This PR watches only the first and lists the rest under additional_deployment_uuids in the response, so the caller can poll them separately via deployment get. Watching all of them concurrently was considered but adds meaningful complexity (parallel polling, combined timeout/status semantics) for a case that's uncommon in practice; the explicit additional_deployment_uuids field keeps the behavior discoverable instead of silent.
Side fix
deployByTagOrUuid was typed as Promise<MessageResponse> ({ message: string }), but Coolify's real /deploy response is { deployments: [{ message, resource_uuid, deployment_uuid }] } (confirmed against docs/openapi-chunks/deployments-api.yaml). This was a latent "wrong-at-runtime" type (same class of bug as #158), and had to be fixed to extract the deployment uuid(s) to poll. New DeployTriggerResponse type added; old { message }-only shape still works for backwards compatibility.
Test mockability
Poll sleeps use an isolated sleep() helper so tests drive them with jest.useFakeTimers() + jest.advanceTimersByTimeAsync() rather than real waits.
Test plan
-
npm test— 369 passed (5 new: success/failure/timeout/no-wait/multi-deployment-first-only) -
npm run lint— clean (pre-existing warnings only, no errors) -
npx prettier --check .— clean -
npm run build— passes - Verified failure-path test asserts the response never contains
server/application/private_key/env_secretfields from a raw upstream payload
🤖 Generated with Claude Code