Commit fda0b51
feat: deploy projects asynchronously with live progress (#1358)
* feat(cli): use the async project deploy API [RED-644]
Switch `checkly deploy` from the synchronous POST /next-v2/projects/deploy to the
async POST /v1/projects/deploy, then poll GET /v1/projects/deployments/{id}/completion
to completion. This removes the API-gateway request-timeout ceiling that caused
large projects to fail with a 504 even though the deploy was still running.
- rest/projects.ts: deploy() submits the deployment and awaits completion
(looping the long-poll completion endpoint, which 408s while in progress);
dry runs still return the preview diff synchronously. Adds ProjectDeployment /
ProjectDeployFailedError types, getDeployment(), and awaitDeploymentCompletion()
with best-effort progress reporting.
- commands/deploy.ts: show a spinner with live progress during the deploy.
The deploy() return shape ({ data: { project, diff } }) is unchanged for callers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
* feat(cli): follow deploys via the SSE progress stream [RED-644]
Replace the completion long-poll (which only refreshed progress ~every 30s) with
the backend's Server-Sent Events stream: deploy() now follows
GET /v1/projects/deployments/{id}/events, surfacing each `progress` frame to
onProgress for a smooth bar and resolving on the terminal `complete` frame. A
single authenticated GET — no client polling.
- streamDeploymentEvents() reconnects (bounded) when the stream drops before a
terminal frame, covering both a clean EOF and a socket error (ECONNRESET) — the
common mid-deploy interruption; the server is stateless so resuming needs no cursor.
- openEventStream() buffers an HTTP error body (the response is a stream, so the
interceptor can't classify it) and re-runs the classifier to surface the typed
error (NotFoundError, etc.).
- Drops the snapshot-on-408 progress hack. dryRun stays the synchronous preview.
Requires the backend SSE route to be deployed first.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
* feat(cli): address deployments under their project [RED-644]
Follow a deploy via /v1/projects/{logicalId}/deployments/{id}/events, threading the
project logicalId (URL-encoded) through the deployment-following calls.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
* feat(deploy): add --cancel-in-progress-deployment to preempt an in-flight deploy [RED-644]
When the async deploy endpoint returns 409 (a deployment is already in progress
for the project), the CLI previously errored out. With the new opt-in flag, the
CLI instead cancels the in-flight deployment, waits for it to finish unwinding,
and retries — so a new deploy can preempt an older one instead of waiting.
- New boolean flag --cancel-in-progress-deployment (default false). It is a
dedicated flag, NOT --force (which only skips the confirmation prompt).
- On a 409 with the flag set, deploy() cancels the specific in-flight
deployment (from the conflict's deploymentId), long-polls the completion
endpoint to a final state, then retries — bounded to avoid an unbounded
cancel war, and treating a vanished predecessor (404) as "slot free".
- awaitDeploymentCompletion floors its poll cadence so a server returning 408
immediately can't become a tight request loop.
- A "Waiting for an in-progress deployment to finish…" status message is shown
during the wait; a 409 without the flag now prints an actionable hint.
- rest/errors.ts: type the deploymentId carried on a 409 ErrorData.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
* feat(deploy): expose cancelRequestedAt on the deployment type [RED-644]
Mirror the API response shape: the deployment now carries cancelRequestedAt
(null unless a cancellation has been requested).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
* feat(deploy): wait for an in-progress deployment by default, then deploy [RED-644]
Previously a 409 (a deployment is already in progress for the project) failed the
deploy. Now `checkly deploy` waits for the in-progress deployment to finish and
then deploys — the same flow as --cancel-in-progress-deployment minus the cancel
step. The flag now means "cancel it instead of waiting".
- On a 409, deploy() resolves the conflict and retries: it long-polls the
completion endpoint until the predecessor reaches a final state (optionally
cancelling it first with the flag), and only then re-POSTs the deploy — once.
The payload is never re-uploaded while the predecessor is still running.
- awaitDeploymentCompletion is a single long-poll; the wait/retry cadence lives
in deploy()/resolveInProgressDeployment, bounded by an overall ~30-min deadline
after which the 409 surfaces with a clear message.
- A predecessor whose worker died is finalized by the backend reaper, after
which the completion poll returns and we deploy.
- Update the flag description and the conflict message to match.
Depends on the backend returning 409 immediately on a concurrent deploy
(previously the request hung), without which neither the wait nor the cancel
path could start.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
* fix(deploy): clearly report a cancelled deployment [RED-644]
A deployment that was cancelled surfaced as the generic "Your project could not
be deployed. / The deployment did not complete successfully." — giving no hint
that it was cancelled.
- Add ProjectDeployCancelledError; submitDeployment throws it when the deploy
stream completes as CANCELLED (a reliable signal, independent of any backend
error text), before the generic non-SUCCEEDED failure path.
- The deploy command reports it distinctly: title "Your deployment was
cancelled." with the body "A newer deployment may have cancelled yours. Try
deploying again if you still need to apply your changes." Still an error
(❌, exit 1) — only the messaging changes.
- Also reword the in-progress (409 past wait deadline) message so the body
stands on its own instead of leaning on the title.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
* style(deploy): plain "..." instead of unicode ellipsis in status messages [RED-644]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
* style(deploy): "an in-progress deployment" in the cancel status message [RED-644]
Matches the wait message; "the" implied a deployment the user hadn't been told about.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
* feat(deploy): type the deploy-result project with id + createdAt/updatedAt
The async/sync deploy response's `result.project` was typed as the bare `Project`
(name/logicalId/repoUrl). Add a `DeployedProject` type matching the API's
deploy-result shape: the `id` it always returned plus the newly added camelCase
`createdAt`/`updatedAt` timestamps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjBjJjMihvJKv8PEzuCvKi
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent fad89ba commit fda0b51
4 files changed
Lines changed: 748 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
| 19 | + | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| |||
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
59 | 64 | | |
60 | 65 | | |
61 | 66 | | |
| |||
83 | 88 | | |
84 | 89 | | |
85 | 90 | | |
| 91 | + | |
86 | 92 | | |
87 | 93 | | |
88 | 94 | | |
| |||
239 | 245 | | |
240 | 246 | | |
241 | 247 | | |
242 | | - | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
243 | 264 | | |
244 | 265 | | |
245 | 266 | | |
| |||
258 | 279 | | |
259 | 280 | | |
260 | 281 | | |
261 | | - | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
262 | 298 | | |
263 | 299 | | |
264 | 300 | | |
| |||
0 commit comments