From 52b3ca29827414ffadea8d900317512ecb6abafb Mon Sep 17 00:00:00 2001 From: fullstackjam Date: Sun, 17 May 2026 12:11:28 +0800 Subject: [PATCH] refactor(ci): split ci.yml into ci.yml + deploy.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separates concerns so badges reflect reality: - ci.yml (name: CI) — runs on PR + push to main, plus repository_dispatch for contract updates. One job (check): type check, tests + coverage, build, contract schema validation. - deploy.yml (name: CD) — triggered via workflow_run after CI succeeds on main. Builds, applies D1 migrations, deploys via wrangler-action, runs the health check + smoke test + post-deploy contract round-trip. README now shows two badges (CI + CD); previously one ambiguous "CI / Deploy" workflow had a single badge that didn't tell the reader whether the issue was a failed test or a failed deploy. Also addresses post-merge review findings on #7: - api-reference.md List Configs example: drop user_id, custom_script, dotfiles_repo, forked_from, created_at — getUserConfigs does not SELECT them (see src/lib/server/db/configs.ts:80). - api-reference.md Get Current User: add avatar_url back — getCurrentUser selects it (src/lib/server/auth.ts:58,70). - api-reference.md install endpoint auth note: replace the vague "browser-friendly auth flow" mention with the actual behavior (404 on the page route for non-owners; no interactive prompt). HARNESS.md: point the post-deploy rows at deploy.yml; clarify the push-to-main → workflow_run → deploy chain in the "not in the harness" section. required-checks.txt is unchanged: `check` and `validate-commits` still exist as job names in ci.yml and conventional-commits.yml. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 65 +------------------------------- .github/workflows/deploy.yml | 72 ++++++++++++++++++++++++++++++++++++ README.md | 3 +- docs/HARNESS.md | 11 +++--- src/docs/api-reference.md | 12 ++---- 5 files changed, 85 insertions(+), 78 deletions(-) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a619ee1..1fba2c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI / Deploy +name: CI on: push: @@ -79,66 +79,3 @@ jobs: sys.exit(1 if failed else 0) " - - deploy: - needs: check - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install dependencies - run: npm install --legacy-peer-deps - - - name: Build - run: npm run build - - - name: Run D1 Migrations - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: npx wrangler d1 migrations apply openboot --remote - - - name: Deploy - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - - - name: Health Check - run: | - echo "Waiting 10 seconds for deployment to propagate..." - sleep 10 - - echo "Running health check..." - HEALTH_RESPONSE=$(curl -s https://openboot.dev/api/health) - echo "Health check response: $HEALTH_RESPONSE" - - STATUS=$(echo $HEALTH_RESPONSE | jq -r '.status') - if [ "$STATUS" != "healthy" ]; then - echo "Health check failed! Status: $STATUS" - echo "Full response: $HEALTH_RESPONSE" - exit 1 - fi - - echo "Health check passed!" - echo "API: $(echo $HEALTH_RESPONSE | jq -r '.checks.api')" - echo "Database: $(echo $HEALTH_RESPONSE | jq -r '.checks.database')" - echo "Version: $(echo $HEALTH_RESPONSE | jq -r '.version')" - - - name: Post-deploy smoke test - run: ./scripts/smoke-test-api.sh https://openboot.dev - - - name: Post-deploy contract validation - run: | - pip install jsonschema - git clone --depth 1 https://github.com/openbootdotdev/openboot-contract.git /tmp/contract - SERVER_URL=https://openboot.dev /tmp/contract/golden-path/contract-smoke.sh diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..9b166ed --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,72 @@ +name: CD + +on: + workflow_run: + workflows: [CI] + types: [completed] + branches: [main] + +jobs: + deploy: + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm install --legacy-peer-deps + + - name: Build + run: npm run build + + - name: Run D1 Migrations + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: npx wrangler d1 migrations apply openboot --remote + + - name: Deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + - name: Health Check + run: | + echo "Waiting 10 seconds for deployment to propagate..." + sleep 10 + + echo "Running health check..." + HEALTH_RESPONSE=$(curl -s https://openboot.dev/api/health) + echo "Health check response: $HEALTH_RESPONSE" + + STATUS=$(echo $HEALTH_RESPONSE | jq -r '.status') + if [ "$STATUS" != "healthy" ]; then + echo "Health check failed! Status: $STATUS" + echo "Full response: $HEALTH_RESPONSE" + exit 1 + fi + + echo "Health check passed!" + echo "API: $(echo $HEALTH_RESPONSE | jq -r '.checks.api')" + echo "Database: $(echo $HEALTH_RESPONSE | jq -r '.checks.database')" + echo "Version: $(echo $HEALTH_RESPONSE | jq -r '.version')" + + - name: Post-deploy smoke test + run: ./scripts/smoke-test-api.sh https://openboot.dev + + - name: Post-deploy contract validation + run: | + pip install jsonschema + git clone --depth 1 https://github.com/openbootdotdev/openboot-contract.git /tmp/contract + SERVER_URL=https://openboot.dev /tmp/contract/golden-path/contract-smoke.sh diff --git a/README.md b/README.md index 215b5ff..c370f5c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Web dashboard and install API for [OpenBoot](https://github.com/openbootdotdev/openboot). [![CI](https://github.com/openbootdotdev/openboot.dev/actions/workflows/ci.yml/badge.svg)](https://github.com/openbootdotdev/openboot.dev/actions/workflows/ci.yml) +[![CD](https://github.com/openbootdotdev/openboot.dev/actions/workflows/deploy.yml/badge.svg)](https://github.com/openbootdotdev/openboot.dev/actions/workflows/deploy.yml) **Live at [openboot.dev](https://openboot.dev)** @@ -41,7 +42,7 @@ GOOGLE_CLIENT_SECRET=... ## Deployment -Push to `main` runs CI (type check + tests + build) and, on success, auto-deploys to [openboot.dev](https://openboot.dev). PRs run CI only. The deploy job lives in `.github/workflows/ci.yml`; see [docs/HARNESS.md](./docs/HARNESS.md) for the full pipeline. +Push to `main` runs CI (`ci.yml`: type check + tests + build + contract validation). On success, CD (`deploy.yml`) fires via `workflow_run` and ships to [openboot.dev](https://openboot.dev) (D1 migrations + wrangler deploy + health check + smoke test). PRs run CI only. See [docs/HARNESS.md](./docs/HARNESS.md) for the full pipeline. Secrets needed: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` diff --git a/docs/HARNESS.md b/docs/HARNESS.md index 142a230..bd56e7d 100644 --- a/docs/HARNESS.md +++ b/docs/HARNESS.md @@ -47,9 +47,9 @@ Three regulation categories: | Behav. | `svelte-check` (TypeScript across `.ts` and `.svelte`) | `npm run check` / `.claude/hooks/stop.sh` / CI | `tsconfig.json` | | Behav. | `vitest run` (unit + smoke) | `npm test` / pre-push / CI | `vitest.config.ts` | | Behav. | `vitest --coverage` → Codecov (informational) | CI | `.github/workflows/ci.yml` | -| Behav. | Contract schema validation against `openboot-contract` | CI `check` job + post-deploy | `.github/workflows/ci.yml` | -| Behav. | Post-deploy health check (`/api/health`) | CI `deploy` job | `.github/workflows/ci.yml` | -| Behav. | Post-deploy smoke test + contract round-trip | CI `deploy` job | `scripts/smoke-test-api.sh` | +| Behav. | Contract schema validation against `openboot-contract` | CI `check` job + post-deploy | `.github/workflows/ci.yml`, `.github/workflows/deploy.yml` | +| Behav. | Post-deploy health check (`/api/health`) | CD `deploy` job | `.github/workflows/deploy.yml` | +| Behav. | Post-deploy smoke test + contract round-trip | CD `deploy` job | `scripts/smoke-test-api.sh` | | Feedfwd. | Agent conventions | every AI turn | `CLAUDE.md`, `AGENTS.md` | | Feedfwd. | Session-start hook (warm `svelte-kit sync`) | every Claude session | `.claude/hooks/session-start.sh` | | Feedfwd. | `ship-pr` skill — push → CI → review → triage → squash → cleanup; **no `--auto`** | model-loaded | `.claude/skills/ship-pr/SKILL.md` | @@ -106,8 +106,9 @@ ideally one rule per PR so the diff is reviewable: up violations directly when a new rule is added. - **No agent-driven changes to `main` without human review.** All AI changes go through PR review and the existing CI matrix. -- **No auto-release / tag automation.** Push to `main` auto-deploys; there - is no separate release cadence to automate. +- **No auto-release / tag automation.** Push to `main` triggers `ci.yml`; + on success `deploy.yml` fires via `workflow_run` and ships to production. + There is no separate release cadence to automate. - **No "stale baseline" sensor.** N/A while there are no baselines. ## How agents should think about this file diff --git a/src/docs/api-reference.md b/src/docs/api-reference.md index 99ffa83..89cce8b 100644 --- a/src/docs/api-reference.md +++ b/src/docs/api-reference.md @@ -50,28 +50,23 @@ GET /api/configs "configs": [ { "id": "cfg_abc123", - "user_id": "usr_xyz", "slug": "my-setup", "name": "My Dev Setup", "description": "Personal development environment", "base_preset": "developer", "packages": [{ "name": "node", "type": "formula" }], - "custom_script": "", - "dotfiles_repo": "", "snapshot": null, "snapshot_at": null, "visibility": "unlisted", "alias": null, "install_count": 12, - "forked_from": null, - "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-02-10T14:20:00Z" } ] } ``` -`packages` and `snapshot` are returned as parsed JSON (not strings). +`packages` and `snapshot` are returned as parsed JSON (not strings). For the full row (including `custom_script`, `dotfiles_repo`, etc.), use `GET /api/configs/:slug`. ### Get Config (Dashboard) @@ -289,7 +284,7 @@ Get the shell install script for a config. The CLI's `install.sh` curls this URL GET /:username/:slug/install ``` -**Auth required:** Only for `private` configs. Send a Bearer token belonging to the owner; otherwise the endpoint returns `403 Config is private` as plain text. (The browser-friendly auth flow for private configs is served via the curl-detection path at `/:username/:slug`, not here.) +**Auth required:** Only for `private` configs. Send a Bearer token belonging to the owner; otherwise the endpoint returns `403 Config is private` as plain text. (The page route `/:username/:slug` returns 404 for non-owners of a private config — there is no interactive auth prompt.) **Response:** Shell script, `Content-Type: text/plain; charset=utf-8`. @@ -461,7 +456,8 @@ GET /api/user "user": { "id": "usr_abc123", "username": "johndoe", - "email": "john@example.com" + "email": "john@example.com", + "avatar_url": "https://avatars.githubusercontent.com/u/123?v=4" } } ```