Skip to content

Commit b6a3cf9

Browse files
fullstackjamclaude
andauthored
refactor(ci): split ci.yml into ci.yml + deploy.yml (#8)
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) <noreply@anthropic.com>
1 parent 6bcc6ec commit b6a3cf9

5 files changed

Lines changed: 85 additions & 78 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: CI / Deploy
1+
name: CI
22

33
on:
44
push:
@@ -79,66 +79,3 @@ jobs:
7979
8080
sys.exit(1 if failed else 0)
8181
"
82-
83-
deploy:
84-
needs: check
85-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
86-
runs-on: ubuntu-latest
87-
permissions:
88-
id-token: write
89-
contents: read
90-
steps:
91-
- uses: actions/checkout@v4
92-
93-
- name: Setup Node.js
94-
uses: actions/setup-node@v4
95-
with:
96-
node-version: '20'
97-
98-
- name: Install dependencies
99-
run: npm install --legacy-peer-deps
100-
101-
- name: Build
102-
run: npm run build
103-
104-
- name: Run D1 Migrations
105-
env:
106-
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
107-
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
108-
run: npx wrangler d1 migrations apply openboot --remote
109-
110-
- name: Deploy
111-
uses: cloudflare/wrangler-action@v3
112-
with:
113-
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
114-
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
115-
116-
- name: Health Check
117-
run: |
118-
echo "Waiting 10 seconds for deployment to propagate..."
119-
sleep 10
120-
121-
echo "Running health check..."
122-
HEALTH_RESPONSE=$(curl -s https://openboot.dev/api/health)
123-
echo "Health check response: $HEALTH_RESPONSE"
124-
125-
STATUS=$(echo $HEALTH_RESPONSE | jq -r '.status')
126-
if [ "$STATUS" != "healthy" ]; then
127-
echo "Health check failed! Status: $STATUS"
128-
echo "Full response: $HEALTH_RESPONSE"
129-
exit 1
130-
fi
131-
132-
echo "Health check passed!"
133-
echo "API: $(echo $HEALTH_RESPONSE | jq -r '.checks.api')"
134-
echo "Database: $(echo $HEALTH_RESPONSE | jq -r '.checks.database')"
135-
echo "Version: $(echo $HEALTH_RESPONSE | jq -r '.version')"
136-
137-
- name: Post-deploy smoke test
138-
run: ./scripts/smoke-test-api.sh https://openboot.dev
139-
140-
- name: Post-deploy contract validation
141-
run: |
142-
pip install jsonschema
143-
git clone --depth 1 https://github.com/openbootdotdev/openboot-contract.git /tmp/contract
144-
SERVER_URL=https://openboot.dev /tmp/contract/golden-path/contract-smoke.sh

.github/workflows/deploy.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: CD
2+
3+
on:
4+
workflow_run:
5+
workflows: [CI]
6+
types: [completed]
7+
branches: [main]
8+
9+
jobs:
10+
deploy:
11+
if: github.event.workflow_run.conclusion == 'success'
12+
runs-on: ubuntu-latest
13+
permissions:
14+
id-token: write
15+
contents: read
16+
steps:
17+
- uses: actions/checkout@v4
18+
with:
19+
ref: ${{ github.event.workflow_run.head_sha }}
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: '20'
25+
26+
- name: Install dependencies
27+
run: npm install --legacy-peer-deps
28+
29+
- name: Build
30+
run: npm run build
31+
32+
- name: Run D1 Migrations
33+
env:
34+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
35+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
36+
run: npx wrangler d1 migrations apply openboot --remote
37+
38+
- name: Deploy
39+
uses: cloudflare/wrangler-action@v3
40+
with:
41+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
42+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
43+
44+
- name: Health Check
45+
run: |
46+
echo "Waiting 10 seconds for deployment to propagate..."
47+
sleep 10
48+
49+
echo "Running health check..."
50+
HEALTH_RESPONSE=$(curl -s https://openboot.dev/api/health)
51+
echo "Health check response: $HEALTH_RESPONSE"
52+
53+
STATUS=$(echo $HEALTH_RESPONSE | jq -r '.status')
54+
if [ "$STATUS" != "healthy" ]; then
55+
echo "Health check failed! Status: $STATUS"
56+
echo "Full response: $HEALTH_RESPONSE"
57+
exit 1
58+
fi
59+
60+
echo "Health check passed!"
61+
echo "API: $(echo $HEALTH_RESPONSE | jq -r '.checks.api')"
62+
echo "Database: $(echo $HEALTH_RESPONSE | jq -r '.checks.database')"
63+
echo "Version: $(echo $HEALTH_RESPONSE | jq -r '.version')"
64+
65+
- name: Post-deploy smoke test
66+
run: ./scripts/smoke-test-api.sh https://openboot.dev
67+
68+
- name: Post-deploy contract validation
69+
run: |
70+
pip install jsonschema
71+
git clone --depth 1 https://github.com/openbootdotdev/openboot-contract.git /tmp/contract
72+
SERVER_URL=https://openboot.dev /tmp/contract/golden-path/contract-smoke.sh

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Web dashboard and install API for [OpenBoot](https://github.com/openbootdotdev/openboot).
44

55
[![CI](https://github.com/openbootdotdev/openboot.dev/actions/workflows/ci.yml/badge.svg)](https://github.com/openbootdotdev/openboot.dev/actions/workflows/ci.yml)
6+
[![CD](https://github.com/openbootdotdev/openboot.dev/actions/workflows/deploy.yml/badge.svg)](https://github.com/openbootdotdev/openboot.dev/actions/workflows/deploy.yml)
67

78
**Live at [openboot.dev](https://openboot.dev)**
89

@@ -41,7 +42,7 @@ GOOGLE_CLIENT_SECRET=...
4142

4243
## Deployment
4344

44-
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.
45+
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.
4546

4647
Secrets needed: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`
4748

docs/HARNESS.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ Three regulation categories:
4747
| Behav. | `svelte-check` (TypeScript across `.ts` and `.svelte`) | `npm run check` / `.claude/hooks/stop.sh` / CI | `tsconfig.json` |
4848
| Behav. | `vitest run` (unit + smoke) | `npm test` / pre-push / CI | `vitest.config.ts` |
4949
| Behav. | `vitest --coverage` → Codecov (informational) | CI | `.github/workflows/ci.yml` |
50-
| Behav. | Contract schema validation against `openboot-contract` | CI `check` job + post-deploy | `.github/workflows/ci.yml` |
51-
| Behav. | Post-deploy health check (`/api/health`) | CI `deploy` job | `.github/workflows/ci.yml` |
52-
| Behav. | Post-deploy smoke test + contract round-trip | CI `deploy` job | `scripts/smoke-test-api.sh` |
50+
| Behav. | Contract schema validation against `openboot-contract` | CI `check` job + post-deploy | `.github/workflows/ci.yml`, `.github/workflows/deploy.yml` |
51+
| Behav. | Post-deploy health check (`/api/health`) | CD `deploy` job | `.github/workflows/deploy.yml` |
52+
| Behav. | Post-deploy smoke test + contract round-trip | CD `deploy` job | `scripts/smoke-test-api.sh` |
5353
| Feedfwd. | Agent conventions | every AI turn | `CLAUDE.md`, `AGENTS.md` |
5454
| Feedfwd. | Session-start hook (warm `svelte-kit sync`) | every Claude session | `.claude/hooks/session-start.sh` |
5555
| 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:
106106
up violations directly when a new rule is added.
107107
- **No agent-driven changes to `main` without human review.** All AI
108108
changes go through PR review and the existing CI matrix.
109-
- **No auto-release / tag automation.** Push to `main` auto-deploys; there
110-
is no separate release cadence to automate.
109+
- **No auto-release / tag automation.** Push to `main` triggers `ci.yml`;
110+
on success `deploy.yml` fires via `workflow_run` and ships to production.
111+
There is no separate release cadence to automate.
111112
- **No "stale baseline" sensor.** N/A while there are no baselines.
112113

113114
## How agents should think about this file

src/docs/api-reference.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,23 @@ GET /api/configs
5050
"configs": [
5151
{
5252
"id": "cfg_abc123",
53-
"user_id": "usr_xyz",
5453
"slug": "my-setup",
5554
"name": "My Dev Setup",
5655
"description": "Personal development environment",
5756
"base_preset": "developer",
5857
"packages": [{ "name": "node", "type": "formula" }],
59-
"custom_script": "",
60-
"dotfiles_repo": "",
6158
"snapshot": null,
6259
"snapshot_at": null,
6360
"visibility": "unlisted",
6461
"alias": null,
6562
"install_count": 12,
66-
"forked_from": null,
67-
"created_at": "2024-01-15T10:30:00Z",
6863
"updated_at": "2024-02-10T14:20:00Z"
6964
}
7065
]
7166
}
7267
```
7368

74-
`packages` and `snapshot` are returned as parsed JSON (not strings).
69+
`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`.
7570

7671
### Get Config (Dashboard)
7772

@@ -289,7 +284,7 @@ Get the shell install script for a config. The CLI's `install.sh` curls this URL
289284
GET /:username/:slug/install
290285
```
291286

292-
**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.)
287+
**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.)
293288

294289
**Response:** Shell script, `Content-Type: text/plain; charset=utf-8`.
295290

@@ -461,7 +456,8 @@ GET /api/user
461456
"user": {
462457
"id": "usr_abc123",
463458
"username": "johndoe",
464-
"email": "john@example.com"
459+
"email": "john@example.com",
460+
"avatar_url": "https://avatars.githubusercontent.com/u/123?v=4"
465461
}
466462
}
467463
```

0 commit comments

Comments
 (0)