feat(tpagectl): implement site create subcommand #548

Merged
fuzzy merged 2 commits from feat/cli-site-create into main 2026-08-05 09:20:06 +00:00
Owner

What

Implements the site create subcommand for tpagectl.

  • Flags (#165/#166): required --name, --owner, --visibility; optional --domain, --preview-enabled.
  • Validation (#167): client-side checks mirroring the daemon — name is a lowercase DNS label ≤63 chars, owner non-empty, visibility one of public/limited/private, domain matches hostname syntax.
  • POST (#168): sends the registration payload to /api/v1/sites and decodes the created site.
  • Display (#169): prints a readable result block (name, owner, visibility, domain, repo URL, preview status).
  • Auth errors (#170): 401/403 responses surface as authentication failed: ….

Supporting refactor: moves the HTTP client context helpers into internal/tpagectl/client (WithContext/FromContext) so subcommands can retrieve the client. FromContext returns an error when no client is present instead of silently returning nil — addressing the footgun filed as #543.

Why

Phase 2 roadmap task #171. First functional site subcommand, validating the client-context pattern for the remaining commands.

Testing

  • Unit tests: request validation table (valid/invalid name, owner, visibility, domain)
  • httptest: happy path (POST body, bearer header, output block), 401 auth error → friendly message, missing required flag error
  • Client context tests: WithContext/FromContext roundtrip, missing-client error
  • go test -race ./... passes
  • golangci-lint run clean
  • pre-commit hooks pass (gofmt, go mod tidy, go test)

Breaking Changes

None.

Notes

FromContext now returns (*Client, error); the root command's Before hook uses client.WithContext. The remaining site subcommands (list/get/delete/update) are still stubs tracked by their own issues.

Closes #165
Closes #166
Closes #167
Closes #168
Closes #169
Closes #170
Closes #171

## What Implements the `site create` subcommand for tpagectl. - **Flags** (#165/#166): required `--name`, `--owner`, `--visibility`; optional `--domain`, `--preview-enabled`. - **Validation** (#167): client-side checks mirroring the daemon — name is a lowercase DNS label ≤63 chars, owner non-empty, visibility one of public/limited/private, domain matches hostname syntax. - **POST** (#168): sends the registration payload to `/api/v1/sites` and decodes the created site. - **Display** (#169): prints a readable result block (name, owner, visibility, domain, repo URL, preview status). - **Auth errors** (#170): 401/403 responses surface as `authentication failed: …`. **Supporting refactor:** moves the HTTP client context helpers into `internal/tpagectl/client` (`WithContext`/`FromContext`) so subcommands can retrieve the client. `FromContext` returns an error when no client is present instead of silently returning nil — addressing the footgun filed as #543. ## Why Phase 2 roadmap task #171. First functional site subcommand, validating the client-context pattern for the remaining commands. ## Testing - [x] Unit tests: request validation table (valid/invalid name, owner, visibility, domain) - [x] httptest: happy path (POST body, bearer header, output block), 401 auth error → friendly message, missing required flag error - [x] Client context tests: `WithContext`/`FromContext` roundtrip, missing-client error - [x] `go test -race ./...` passes - [x] `golangci-lint run` clean - [x] pre-commit hooks pass (gofmt, go mod tidy, go test) ## Breaking Changes None. ## Notes `FromContext` now returns `(*Client, error)`; the root command's `Before` hook uses `client.WithContext`. The remaining site subcommands (list/get/delete/update) are still stubs tracked by their own issues. Closes #165 Closes #166 Closes #167 Closes #168 Closes #169 Closes #170 Closes #171
Add the site create subcommand: required --name, --owner, --visibility
flags, optional --domain and --preview-enabled, client-side validation
mirroring the daemon rules, POST to /api/v1/sites, result display, and
friendly handling of 401/403 auth errors.

Expose the HTTP client from context via client.WithContext/FromContext so
subcommands can retrieve it; FromContext returns an error when absent,
addressing the nil-return footgun raised in review.
docs(roadmap): mark site create subcommand complete
All checks were successful
Test and Release / lint (pull_request) Successful in 7m7s
Test and Release / test (pull_request) Successful in 7m21s
9da8b8aa05
Record completion of the tpagectl site create subcommand: required and
optional flags, input validation, POST to /api/v1/sites, result display,
and graceful authentication error handling.

closes #165
closes #166
closes #167
closes #168
closes #169
closes #170
closes #171
fuzzy scheduled this pull request to auto merge when all checks succeed 2026-08-05 08:41:19 +00:00
the.auditor left a comment

Summary

site create subcommand for tpagectl. Verified: go test -race ./... passes (including internal/tpagectl/site) and golangci-lint run ./... is clean. Closes #165-171.

Verified daemon parity: createRequest matches RegisterSiteRequest (service.go:16), createResponse matches Site (site.go:20), and validation in buildCreateRequest mirrors validateRequest (service.go:356) — same regexes, ≤63-char limit, and identical error strings.

No blocking issues.

Suggestions (filed as issues)

  1. cmd/tpagectl/main.go:124clientFromContext still swallows the FromContext error and returns nil, keeping the #543 footgun alive for the old accessor; consider removing it and using client.FromContext directly. Filed as #549
  2. internal/tpagectl/site/create.go:91 – 401 and 403 both render as "authentication failed", but daemon 403 means "not authorized to register for this owner" (authorization); also 403 and non-auth passthrough paths are untested. Filed as #550

Praise

  • Exact parity with the daemon's payloads and validation rules — including error strings
  • Clean WithContext/FromContext error-returning pattern addressing #543
  • Solid httptest coverage: request body, bearer header, output block, 401 handling, and a thorough validation table
  • Required flags plus buildCreateRequest validation is good belt-and-suspenders
## Summary `site create` subcommand for tpagectl. Verified: `go test -race ./...` passes (including `internal/tpagectl/site`) and `golangci-lint run ./...` is clean. Closes #165-171. Verified daemon parity: `createRequest` matches `RegisterSiteRequest` (service.go:16), `createResponse` matches `Site` (site.go:20), and validation in `buildCreateRequest` mirrors `validateRequest` (service.go:356) — same regexes, ≤63-char limit, and identical error strings. No blocking issues. ## Suggestions (filed as issues) 1. `cmd/tpagectl/main.go:124` – `clientFromContext` still swallows the `FromContext` error and returns nil, keeping the #543 footgun alive for the old accessor; consider removing it and using `client.FromContext` directly. Filed as #549 2. `internal/tpagectl/site/create.go:91` – 401 and 403 both render as "authentication failed", but daemon 403 means "not authorized to register for this owner" (authorization); also 403 and non-auth passthrough paths are untested. Filed as #550 ## Praise - Exact parity with the daemon's payloads and validation rules — including error strings - Clean `WithContext`/`FromContext` error-returning pattern addressing #543 - Solid httptest coverage: request body, bearer header, output block, 401 handling, and a thorough validation table - `Required` flags plus `buildCreateRequest` validation is good belt-and-suspenders
fuzzy merged commit 9da8b8aa05 into main 2026-08-05 09:20:06 +00:00
fuzzy deleted branch feat/cli-site-create 2026-08-05 09:20:06 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 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!548
No description provided.