feat(tpagectl): implement site list subcommand #551

Merged
fuzzy merged 4 commits from feat/cli-site-list into main 2026-08-05 11:08:46 +00:00
Owner

What

Implements site list end to end: the daemon's missing GET /api/v1/sites endpoint plus the tpagectl site list subcommand.

Daemon

  • SiteService.List returns all sites with their current deployments; SiteStatus gains a url field populated via the new KubernetesClient.SiteURL.
  • GET /api/v1/sites handler returns []SiteStatus, scoped to the authenticated user's sites (admins see all; no-auth daemons return all).
  • Tests: service List (deployments + URLs), handler list/error/owner-scoping.

CLI

  • site list fetches /api/v1/sites and renders a table of Name, Owner, Status, Domain, URL (text/tabwriter).
  • --format (table default, json, yaml) — #199; gopkg.in/yaml.v3 promoted to a direct dependency.
  • --filter owner=<name> and/or --filter status=<status> (repeatable) — #175.
  • Tests: table/json/yaml output, owner and status filters, invalid filter/format errors.

Why

Phase 2 roadmap task #176. The daemon had no site-list endpoint, so this delivers the CLI command and its prerequisite API together.

Testing

  • Daemon: service List test, handler OK/error/owner-scoping tests
  • CLI: table columns, JSON round-trip, YAML output, owner/status filters, invalid filter/format
  • go test -race ./... passes (17 packages)
  • golangci-lint run clean
  • pre-commit hooks pass (gofmt, go mod tidy, go test)

Breaking Changes

None. SiteStatus gains an optional url JSON field (omitempty).

Notes

#199 (--format) is also referenced by deployment list (#200); the implementation here is reusable there later.

Closes #172
Closes #173
Closes #175
Closes #176
Closes #199

## What Implements `site list` end to end: the daemon's missing `GET /api/v1/sites` endpoint plus the tpagectl `site list` subcommand. **Daemon** - `SiteService.List` returns all sites with their current deployments; `SiteStatus` gains a `url` field populated via the new `KubernetesClient.SiteURL`. - `GET /api/v1/sites` handler returns `[]SiteStatus`, scoped to the authenticated user's sites (admins see all; no-auth daemons return all). - Tests: service `List` (deployments + URLs), handler list/error/owner-scoping. **CLI** - `site list` fetches `/api/v1/sites` and renders a table of Name, Owner, Status, Domain, URL (`text/tabwriter`). - `--format` (table default, json, yaml) — #199; `gopkg.in/yaml.v3` promoted to a direct dependency. - `--filter owner=<name>` and/or `--filter status=<status>` (repeatable) — #175. - Tests: table/json/yaml output, owner and status filters, invalid filter/format errors. ## Why Phase 2 roadmap task #176. The daemon had no site-list endpoint, so this delivers the CLI command and its prerequisite API together. ## Testing - [x] Daemon: service List test, handler OK/error/owner-scoping tests - [x] CLI: table columns, JSON round-trip, YAML output, owner/status filters, invalid filter/format - [x] `go test -race ./...` passes (17 packages) - [x] `golangci-lint run` clean - [x] pre-commit hooks pass (gofmt, go mod tidy, go test) ## Breaking Changes None. `SiteStatus` gains an optional `url` JSON field (`omitempty`). ## Notes `#199` (`--format`) is also referenced by `deployment list` (#200); the implementation here is reusable there later. Closes #172 Closes #173 Closes #175 Closes #176 Closes #199
Add the site list endpoint and the tpagectl site list subcommand.

Daemon: SiteService gains List, returning all sites with current
deployments and public URLs. SiteStatus carries a url field populated via
the new KubernetesClient.SiteURL, and GET /api/v1/sites scopes the
response to the authenticated user's sites (admins see all).

CLI: site list fetches /api/v1/sites and renders a table of name, owner,
status, domain, and URL, with --format (table, json, yaml) and repeatable
--filter (owner, status) support. gopkg.in/yaml.v3 is promoted to a
direct dependency for YAML output.
docs(roadmap): mark site list subcommand complete
Some checks failed
Test and Release / test (pull_request) Has been cancelled
Test and Release / lint (pull_request) Has been cancelled
e38ae336a2
Record completion of the tpagectl site list subcommand and its daemon
endpoint: GET /api/v1/sites, table output, --format, and --filter.

closes #172
closes #173
closes #175
closes #176
closes #199
fuzzy scheduled this pull request to auto merge when all checks succeed 2026-08-05 09:49:51 +00:00
the.auditor requested changes 2026-08-05 09:53:14 +00:00
Dismissed
the.auditor left a comment

Summary

Site list: daemon GET /api/v1/sites + tpagectl site list. Verified: golangci-lint run ./... clean; full-suite go test -race ./... passes intermittently — internal/site fails under repeated runs. Closes #172/#173/#175/#176/#199.

Blocking Issues

  1. internal/site/service_test.go:468 (TestList) + service.go:343 (List) – non-deterministic ordering. MemoryRepository.List (memory.go:51) iterates a map, so Service.List/handler return sites in random order. TestList assumes insertion order and is flaky — reproduced with go test -race ./internal/site/... -count=5:
    service_test.go:503: blog URL = "https://docs.pages.example.com", want "https://blog.example.com"
    
    This also makes GET /api/v1/sites and site list output nondeterministic. Suggested fix: sort sites deterministically (e.g. by name) in the repository List or Service.List, and make the test order-independent.

Suggestions (filed as issues)

  1. internal/tpagectl/site/list.go:49--format yaml keys diverge from JSON/API keys (repourl, customdomain, currentdeployment, previewenabled, createdat) because the structs only carry json: tags. Filed as #552

Praise

  • Deployment payload parity verified (JSON tags match exactly)
  • Correct owner-scoping in the list handler; no-auth returns all, authed returns own, empty result is [] not null
  • Clean --format/--filter implementation with a reusable filter pipeline
  • yaml.v3 correctly promoted to a direct dependency, no new modules
## Summary Site list: daemon `GET /api/v1/sites` + tpagectl `site list`. Verified: `golangci-lint run ./...` clean; full-suite `go test -race ./...` passes intermittently — `internal/site` fails under repeated runs. Closes #172/#173/#175/#176/#199. ## Blocking Issues 1. `internal/site/service_test.go:468` (`TestList`) + `service.go:343` (`List`) – non-deterministic ordering. `MemoryRepository.List` (memory.go:51) iterates a map, so `Service.List`/handler return sites in random order. `TestList` assumes insertion order and is flaky — reproduced with `go test -race ./internal/site/... -count=5`: ``` service_test.go:503: blog URL = "https://docs.pages.example.com", want "https://blog.example.com" ``` This also makes `GET /api/v1/sites` and `site list` output nondeterministic. Suggested fix: sort sites deterministically (e.g. by name) in the repository `List` or `Service.List`, and make the test order-independent. ## Suggestions (filed as issues) 1. `internal/tpagectl/site/list.go:49` – `--format yaml` keys diverge from JSON/API keys (`repourl`, `customdomain`, `currentdeployment`, `previewenabled`, `createdat`) because the structs only carry `json:` tags. Filed as #552 ## Praise - Deployment payload parity verified (JSON tags match exactly) - Correct owner-scoping in the list handler; no-auth returns all, authed returns own, empty result is `[]` not `null` - Clean `--format`/`--filter` implementation with a reusable filter pipeline - `yaml.v3` correctly promoted to a direct dependency, no new modules
Service.List returned sites in map iteration order from the memory
repository, making GET /api/v1/sites and site list output nondeterministic
and TestList flaky. Sort by site name and make the test order-independent.
fix(tpagectl): use snake_case keys in site list yaml output
All checks were successful
Test and Release / lint (pull_request) Successful in 6m56s
Test and Release / test (pull_request) Successful in 7m14s
89c83f5874
Add yaml tags to the site list response structs so --format yaml emits the
same keys as the JSON/API payload (repo_url, custom_domain,
current_deployment, etc.) instead of yaml.v3's default lowercased field
names.

closes #552
Author
Owner

Addressed the requested changes.

Blocking (non-deterministic ordering): Service.List now sorts sites by name, so GET /api/v1/sites and site list output is deterministic. TestList is order-independent (looks up sites by name). Verified with go test -race -count=5 ./internal/site/.

Also fixed (filed as #552): added yaml: tags to the site list response structs so --format yaml emits snake_case keys matching the JSON/API payload (repo_url, custom_domain, current_deployment, …); TestListYAML now asserts repo_url:/current_deployment:.

Verified: go test -race ./... (17 packages), golangci-lint run, and pre-commit all pass.

Addressed the requested changes. **Blocking (non-deterministic ordering):** `Service.List` now sorts sites by name, so `GET /api/v1/sites` and `site list` output is deterministic. `TestList` is order-independent (looks up sites by name). Verified with `go test -race -count=5 ./internal/site/`. **Also fixed (filed as #552):** added `yaml:` tags to the site list response structs so `--format yaml` emits snake_case keys matching the JSON/API payload (`repo_url`, `custom_domain`, `current_deployment`, …); `TestListYAML` now asserts `repo_url:`/`current_deployment:`. Verified: `go test -race ./...` (17 packages), `golangci-lint run`, and pre-commit all pass.
the.auditor left a comment

Summary

Re-review after the fix commits. Verified: go test -race ./... passes (17 packages), golangci-lint run ./... clean, and the previously-flaky internal/site suite passes consistently under -count=5. Closes #172/#173/#175/#176/#199.

Blocking Issues

Resolved. Service.List now sorts statuses by name, making GET /api/v1/sites and site list output deterministic. TestList is order-independent (looks up sites by name). Reproduced the prior flake with go test -race -count=5 ./internal/site/ before the fix; it is stable now.

Previously Filed Suggestions

  1. #552 — resolved: yaml: tags added to listResponse/listDeployment; TestListYAML now asserts repo_url: and current_deployment:.

Praise

  • Deterministic ordering handled at the service layer, benefiting API and CLI consumers uniformly
  • yaml: tags mirror JSON tags exactly, keeping format keys consistent
  • Test strengthened rather than weakened (name-lookup map + new key assertions)
## Summary Re-review after the fix commits. Verified: `go test -race ./...` passes (17 packages), `golangci-lint run ./...` clean, and the previously-flaky `internal/site` suite passes consistently under `-count=5`. Closes #172/#173/#175/#176/#199. ## Blocking Issues Resolved. `Service.List` now sorts statuses by name, making `GET /api/v1/sites` and `site list` output deterministic. `TestList` is order-independent (looks up sites by name). Reproduced the prior flake with `go test -race -count=5 ./internal/site/` before the fix; it is stable now. ## Previously Filed Suggestions 1. #552 — resolved: `yaml:` tags added to `listResponse`/`listDeployment`; `TestListYAML` now asserts `repo_url:` and `current_deployment:`. ## Praise - Deterministic ordering handled at the service layer, benefiting API and CLI consumers uniformly - `yaml:` tags mirror JSON tags exactly, keeping format keys consistent - Test strengthened rather than weakened (name-lookup map + new key assertions)
fuzzy merged commit 89c83f5874 into main 2026-08-05 11:08:46 +00:00
fuzzy deleted branch feat/cli-site-list 2026-08-05 11:08:47 +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!551
No description provided.