Initial Commit
This commit is contained in:
164
openspec/changes/react-ui-registry-deployment/tasks.md
Normal file
164
openspec/changes/react-ui-registry-deployment/tasks.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# 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/`.
|
||||
Reference in New Issue
Block a user