feat(tpagectl): implement deployment promote subcommand #568

Merged
fuzzy merged 3 commits from feat/cli-deployment-promote into main 2026-08-06 05:48:42 +00:00
Owner

What

Implements deployment promote end to end: the daemon's missing POST /api/v1/sites/{name}/deploy endpoint plus the tpagectl deployment promote subcommand.

Daemon

  • SiteService.Publish(ctx, siteName, version) now accepts an optional version: empty selects the highest v* tag (preserving the webhook auto-deploy path, which passes ""); a non-empty version must be an existing tag of the site's repository, else a validation error (→ 400).
  • New POST /api/v1/sites/{name}/deploy handler (#206): owner-scoped (403), 200 with the new Deployment, 400 on invalid body / unknown tag / no tags, 404 when the site is missing. isValidationError now also maps "has no …" messages.
  • Tests: service (auto-highest, specific version, unknown tag, no tags, unknown site) and handler (200/400/404).

CLI

  • Flags (#205): --site (required), --version (optional; omitted → highest tag).
  • POST (#206): /api/v1/sites/{name}/deploy body {"version": ...}.
  • Display (#208): prints Deploying vX..., then Deployed vY plus the site URL (fetched from the site status endpoint).
  • Friendly 404s; missing --site error.
  • Tests: version happy path (verifies path + POST body + version/URL output), default version ({"version":""}), no-URL fallback, site 404, missing --site.

Why

Phase 3 roadmap task #209.

Testing

  • Daemon: service + handler tests for versioned/auto publish, unknown tag, no tags, 404, 400
  • CLI: promote happy path, default version, no-URL fallback, 404, missing --site
  • go test -race ./... passes (18 packages)
  • golangci-lint run clean
  • pre-commit hooks pass (gofmt, go mod tidy, go test)

Breaking Changes

SiteService.Publish signature gains a version parameter; the webhook receiver and test fakes were updated accordingly (empty string preserves existing auto-deploy behavior).

Notes

#207 (deployment progress spinner) is deferred to Phase 7 progress feedback (#266) — deploy is a synchronous call, so a Deploying vX... status line is printed instead. Branch rebased onto current main (includes deployment get #567).

Closes #205
Closes #206
Closes #208
Closes #209

## What Implements `deployment promote` end to end: the daemon's missing `POST /api/v1/sites/{name}/deploy` endpoint plus the tpagectl `deployment promote` subcommand. **Daemon** - `SiteService.Publish(ctx, siteName, version)` now accepts an optional version: empty selects the highest `v*` tag (preserving the webhook auto-deploy path, which passes `""`); a non-empty version must be an existing tag of the site's repository, else a validation error (→ 400). - New `POST /api/v1/sites/{name}/deploy` handler (#206): owner-scoped (403), 200 with the new `Deployment`, 400 on invalid body / unknown tag / no tags, 404 when the site is missing. `isValidationError` now also maps "has no …" messages. - Tests: service (auto-highest, specific version, unknown tag, no tags, unknown site) and handler (200/400/404). **CLI** - **Flags** (#205): `--site` (required), `--version` (optional; omitted → highest tag). - **POST** (#206): `/api/v1/sites/{name}/deploy` body `{"version": ...}`. - **Display** (#208): prints `Deploying vX...`, then `Deployed vY` plus the site URL (fetched from the site status endpoint). - Friendly 404s; missing `--site` error. - Tests: version happy path (verifies path + POST body + version/URL output), default version (`{"version":""}`), no-URL fallback, site 404, missing `--site`. ## Why Phase 3 roadmap task #209. ## Testing - [x] Daemon: service + handler tests for versioned/auto publish, unknown tag, no tags, 404, 400 - [x] CLI: promote happy path, default version, no-URL fallback, 404, missing `--site` - [x] `go test -race ./...` passes (18 packages) - [x] `golangci-lint run` clean - [x] pre-commit hooks pass (gofmt, go mod tidy, go test) ## Breaking Changes `SiteService.Publish` signature gains a `version` parameter; the webhook receiver and test fakes were updated accordingly (empty string preserves existing auto-deploy behavior). ## Notes #207 (deployment progress spinner) is deferred to Phase 7 progress feedback (#266) — deploy is a synchronous call, so a `Deploying vX...` status line is printed instead. Branch rebased onto current main (includes `deployment get` #567). Closes #205 Closes #206 Closes #208 Closes #209
- Add POST /api/v1/sites/:name/deploy endpoint to promote a site deployment
- Support optional version parameter, defaulting to highest available tag
- Implement CLI promote command with --site and --version flags
- Update Publish service method to accept version parameter
- Add error handling for unknown version tags and missing sites
- Add validation for sites without matching version tags
Add the daemon POST /api/v1/sites/{name}/deploy endpoint and the tpagectl
deployment promote subcommand.

Daemon: SiteService.Publish now takes an optional version - empty selects
the highest v* tag (preserving webhook auto-deploy), otherwise the version
must be an existing tag. The promote handler is owner-scoped and returns
200 with the new deployment, 400 on unknown tags or no tags, 404 when the
site is missing.

CLI: deployment promote takes --site (required) and optional --version
(omitted uses the highest tag), posts to the deploy endpoint, and prints
the new active version and the site URL.
docs(roadmap): mark deployment promote subcommand complete
Some checks failed
Test and Release / test (pull_request) Has been cancelled
Test and Release / lint (pull_request) Has been cancelled
25dc8fc510
Record completion of the tpagectl deployment promote subcommand and its
daemon endpoint: --site with optional --version, POST
/api/v1/sites/{name}/deploy, and display of the new active version and
URL. Deployment progress spinner (#207) is deferred to Phase 7 progress
feedback.

closes #205
closes #206
closes #208
closes #209
fuzzy force-pushed feat/cli-deployment-promote from 25dc8fc510
Some checks failed
Test and Release / test (pull_request) Has been cancelled
Test and Release / lint (pull_request) Has been cancelled
to 2453db3b99
All checks were successful
Test and Release / lint (pull_request) Successful in 7m54s
Test and Release / test (pull_request) Successful in 8m8s
2026-08-06 05:40:30 +00:00
Compare
fuzzy scheduled this pull request to auto merge when all checks succeed 2026-08-06 05:40:39 +00:00
the.auditor left a comment

Summary

deployment promote: daemon POST /api/v1/sites/{name}/deploy + tpagectl subcommand, with SiteService.Publish gaining a version parameter. Verified: go test -race ./... passes (18 packages) and golangci-lint run ./... is clean. Closes #205/#206/#208/#209; #207 (spinner) deferred to Phase 7.

Verified correctness: explicit --version must match an existing tag (else 400 via "has no " mapping), empty version selects the highest v* tag (webhook auto-deploy path preserved via Publish(..., "")), handler maps ErrNotFound→404; deploy response is the daemon Deployment, matching deploymentRecord.

No blocking issues.

Suggestions (filed as issues)

  1. internal/tpagectl/deployment/promote.go:60 – the site-status GET used for the URL is silently best-effort; a failed URL fetch is swallowed with no indication. Filed as #569

Questions

  1. internal/site/service.go (selectVersion) – an explicit version may be any existing repo tag, including non-v tags. isPreviewDeployment (!HasPrefix(d.Version, "v")) would then classify such a production deployment as a preview during site deletion. Is promoting non-v tags intended? If so, consider whether the preview-vs-production detection needs to stay aligned.

Praise

  • Clean selectVersion extraction; empty-version semantics preserve the webhook path
  • Breaking Publish signature change handled cleanly across the webhook receiver and all test fakes
  • "has no " → 400 handler mapping is correct
  • Good CLI UX (Deploying…/Deployed + URL) with a graceful no-URL fallback
  • Honest #207 spinner deferral left unchecked in the roadmap
## Summary `deployment promote`: daemon `POST /api/v1/sites/{name}/deploy` + tpagectl subcommand, with `SiteService.Publish` gaining a version parameter. Verified: `go test -race ./...` passes (18 packages) and `golangci-lint run ./...` is clean. Closes #205/#206/#208/#209; #207 (spinner) deferred to Phase 7. Verified correctness: explicit `--version` must match an existing tag (else 400 via "has no " mapping), empty version selects the highest `v*` tag (webhook auto-deploy path preserved via `Publish(..., "")`), handler maps ErrNotFound→404; deploy response is the daemon `Deployment`, matching `deploymentRecord`. No blocking issues. ## Suggestions (filed as issues) 1. `internal/tpagectl/deployment/promote.go:60` – the site-status GET used for the URL is silently best-effort; a failed URL fetch is swallowed with no indication. Filed as #569 ## Questions 1. `internal/site/service.go` (`selectVersion`) – an explicit version may be any existing repo tag, including non-`v` tags. `isPreviewDeployment` (`!HasPrefix(d.Version, "v")`) would then classify such a production deployment as a preview during site deletion. Is promoting non-v tags intended? If so, consider whether the preview-vs-production detection needs to stay aligned. ## Praise - Clean `selectVersion` extraction; empty-version semantics preserve the webhook path - Breaking `Publish` signature change handled cleanly across the webhook receiver and all test fakes - "has no " → 400 handler mapping is correct - Good CLI UX (Deploying…/Deployed + URL) with a graceful no-URL fallback - Honest #207 spinner deferral left unchecked in the roadmap
fuzzy merged commit 2453db3b99 into main 2026-08-06 05:48:42 +00:00
fuzzy deleted branch feat/cli-deployment-promote 2026-08-06 05:48:42 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
thwap/thwap-pagesd!568
No description provided.