Files
2026-05-27 23:29:53 +01:00

7.7 KiB

tasks — react-ui-registry-deployment

0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Registry build script (in @suaveplan/react-ui)

  • 1.1 Create packages/react/react-ui/scripts/build-registry.ts. Walks the package's component source tree and emits one registry-item JSON per component plus a top-level registry/r/index.json listing every entry.
  • 1.2 Match the shadcn registry-item v1 schema exactly: $schema, name, type, dependencies, registryDependencies, files[], tailwind, cssVars. Validate the emitted JSON against a Zod schema before writing — the build fails on shape drift.
  • 1.3 Emit deterministic output: stable key order, LF line endings, trailing newline. The CI snapshot diff in the package's existing test suite will catch accidental drift.
  • 1.4 Co-locate build-registry.md per the genesis docs rule (≥200 words, Purpose / Features / Basic Example / Advanced Example / API Reference / Implementation Notes).
  • 1.5 Add a smoke test packages/react/react-ui/scripts/build-registry.test.ts that invokes the script in a temp dir, parses every emitted JSON, and asserts schema conformance.

2. Caddy + Docker scaffolding

  • 2.1 Write ops/registry/Dockerfile — FROM caddy:2-alpine, COPY Caddyfile /etc/caddy/Caddyfile, healthcheck on /health.
  • 2.2 Write ops/registry/Caddyfile — ui.suaveplan.com site block with:
    • root * /srv/registry
    • handle_path /r/* → file_server with Access-Control-Allow-Origin: *
    • handle_path /storybook/* → file_server with restrictive CSP
    • handle /health → respond 200 "ok"
    • tls block pinning protocols tls1.2 tls1.3
    • header block with HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
    • encode gzip zstd
    • explicit deny on /.well-known/* outside ACME
  • 2.3 Write ops/registry/docker-compose.yml — one service registry, port 80:80 and 443:443, named volumes caddy_data and caddy_config, bind-mount /srv/registry:/srv/registry:ro, restart: unless-stopped.
  • 2.4 Write ops/registry/.env.example — placeholders OVH_SSH_HOST=__OVH_HOST__, OVH_SSH_USER=deploy-registry, REGISTRY_DEPLOY_PATH=/srv/registry/r/, STORYBOOK_DEPLOY_PATH=/srv/registry/storybook/. No real values.

3. Deployment runbook

  • 3.1 Write ops/registry/README.md covering:
    • Architecture overview (one Caddy container, two paths)
    • First-time setup (clone, env, docker compose up -d, cert issuance check)
    • Creating the deploy-registry user with scoped sudoers rule
    • Installing the CI public key on OVH
    • The on-deploy flow (path-filtered GitHub Action, rsync, no-reload-needed-for-static)
    • Manual ad-hoc admin via ssh root@ovh (out-of-band; CI intentionally does not use root)
    • Rollback via bash ops/registry/rollback.sh (one-shot atomic swap from /srv/registry/.previous/<timestamp>/)
    • Cert renewal (Caddy handles automatically; how to verify and how to force-renew if needed)
    • GitHub Actions secret rotation steps
  • 3.2 Write ops/registry/rollback.sh referenced from the runbook. Idempotent, exits non-zero with a clear message if no snapshot exists.

4. CI workflow

  • 4.1 Write .github/workflows/registry-deploy.yml — triggers on push to genesis with path filter packages/react/react-ui/** plus ops/registry/**. Job steps:
    • actions/checkout@v4
    • Setup Bun via oven-sh/setup-bun@v2
    • bun install --frozen-lockfile
    • bunx turbo build --filter=@suaveplan/react-ui
    • bun packages/react/react-ui/scripts/build-registry.ts
    • cd packages/react/react-ui && bun run build-storybook
    • webfactory/ssh-agent@v0.9.0 with the OVH_SSH_PRIVATE_KEY secret
    • rsync -azh --delete --backup --backup-dir=/srv/registry/.previous/$(date -u +%Y%m%dT%H%M%SZ)/r/ packages/react/react-ui/registry/r/ "$OVH_SSH_USER@$OVH_SSH_HOST:/srv/registry/r/"
    • same for storybook with its own --backup-dir
    • post-deploy curl -fsSL https://ui.suaveplan.com/health smoke test
  • 4.2 Add a concurrency: block keyed on the workflow + ref so back-to-back pushes serialise, never race.
  • 4.3 Document the three required GitHub Actions secrets in the runbook (OVH_SSH_PRIVATE_KEY, OVH_SSH_HOST, OVH_SSH_USER). NEVER commit values.

5. Manual user steps (call out clearly)

The user MUST perform these one-time manual steps; they cannot be scripted in this change because they involve secrets and OVH host-side configuration.

  • 5.1 USER: Generate a deploy SSH keypair locally (ssh-keygen -t ed25519 -C "registry-deploy" -f ~/.ssh/registry_deploy).
  • 5.2 USER: Copy the public key to OVH under the deploy-registry user's ~/.ssh/authorized_keys (after creating the user per the runbook).
  • 5.3 USER: Add the private key as the OVH_SSH_PRIVATE_KEY secret in the GitHub repo settings.
  • 5.4 USER: Add OVH_SSH_HOST and OVH_SSH_USER as GitHub Actions secrets (host = the OVH hostname, user = deploy-registry).
  • 5.5 USER: Confirm ui.suaveplan.com A record points to the OVH IP (already done per change context, but re-verify with dig +short ui.suaveplan.com before first deploy).

6. Verification (Web App & Page Verification gate)

Per CLAUDE.md "Web App & Page Verification" — required even though this is mostly static content.

  • 6.1 Deploy from a feature branch first (NOT genesis) against a staging path or a temporary subdomain to validate the pipeline before pointing it at production.
  • 6.2 Run a Playwright sweep: navigate to /, /r/index.json, /storybook/, plus a sampled subset of per-component manifests. Assert HTTPS, valid cert, expected DOM content, zero console.error, zero failed subresource requests.
  • 6.3 End-to-end shadcn-add test: spin up a clean Vite + React project in a temp dir, point its components.json at the public registry, run npx shadcn@latest add @suaveplan/button, assert src/components/ui/button.tsx lands with the expected exports. This is the contract test.
  • 6.4 Capture browser console + network during the Playwright sweep; any unexpected warning or 4xx/5xx fails the gate.
  • 6.5 Run the rollback script once on the OVH host and verify the previous registry contents serve. Roll forward again before closing the task.
  • 6.6 24 h after first production deploy, query the Caddy access log and confirm 5xx count is zero.

7. Documentation + change archive

  • 7.1 Refresh root docs per AGENTS §13b: MANIFEST.md, MISSING.yml, STATUS.md, SUMMARY.md. The STATUS.md row for ops infrastructure should reflect that the registry deploy exists.
  • 7.2 Run bunx markdownlint-cli2 --fix on every touched Markdown file; resolve any unfixable violations by hand.
  • 7.3 Fold specs/registry-deployment/spec.md into the canonical openspec/specs/registry-deployment/spec.md (a new capability spec — first ops-tier capability on genesis).
  • 7.4 Move openspec/changes/react-ui-registry-deployment/ to openspec/archive/<yyyy-mm-dd>-react-ui-registry-deployment/.