返回 CodeWhale
README.md
根目录 / web / README.md
1 # codewhale-web
2
3 Documentation and community site for [Codewhale](https://github.com/codewhale-hq/CodeWhale) — lives at **codewhale.net**.
4
5 Next.js 15 (App Router) + Tailwind, deployed to Cloudflare Workers via [`@opennextjs/cloudflare`](https://opennext.js.org/cloudflare). Curated "Today's Dispatch" content is regenerated every 6 hours by a Cloudflare Cron Trigger that calls `deepseek-flash` to summarise recent repo activity, and stored in Workers KV.
6
7 ## Local dev
8
9 ```bash
10 cd web
11 npm install
12 cp .env.example .env.local # fill in the keys you have
13 npm run dev # http://localhost:3000
14 ```
15
16 Env (mirrors `.env.example`):
17
18 | Variable | What | Required? |
19 | --------------------------- | ---------------------------------------------------------------- | -------------------- |
20 | `DEEPSEEK_API_KEY` | DeepSeek platform key (`sk-...`) | only for the `/api/cron` tasks (summarization + community agent) |
21 | `GITHUB_TOKEN` | Fine-grained PAT, public-repo read scope | optional (raises rate limit 60 → 5000 req/h) |
22 | `GITHUB_REPO` | Defaults to `codewhale-hq/CodeWhale` | optional |
23 | `CRON_SECRET` | Shared secret for manual `/api/cron` invocation | optional (Cloudflare cron triggers don't need it) |
24 | `DEEPSEEK_MODEL` | Defaults to `deepseek-flash` | optional |
25 | `DEEPSEEK_BASE_URL` | Defaults to `https://api.deepseek.com` | optional |
26 | `MAINTAINER_TOKEN` | Admin panel auth; enter it in the `/admin` login form | only for `/admin` |
27 | `MAINTAINER_GITHUB_PAT` | PAT with `issues:write`, for posting comments via `/admin` | only for `/admin` posting |
28 | `NEXT_PUBLIC_GITEE_ENABLED` | Set to `1` once the Gitee mirror exists; blank hides Gitee links | optional |
29
30 The site renders fine without any of them — `Today's Dispatch` falls back to a static editorial; the GitHub feed shows "feed not yet loaded".
31
32 ## Deploy to Cloudflare
33
34 Ordinary pushes and pull requests run the web checks and production build, but
35 they do **not** deploy. The `deploy` job in `.github/workflows/web.yml` runs
36 only for a maintainer-triggered `workflow_dispatch` on `main`. Before approval,
37 record the exact 40-character `origin/main` SHA and trigger that ref:
38
39 ```bash
40 git fetch origin main
41 git rev-parse origin/main
42 gh workflow run web.yml --repo codewhale-hq/CodeWhale --ref main
43 ```
44
45 Every green push to `main` also emits a `Deployment approval needed` workflow
46 notice with that command. The reminder does not receive Cloudflare credentials
47 and cannot deploy; it keeps the manual production approval boundary visible.
48
49 The manual job records the pre-deploy source drift, builds the OpenNext bundle,
50 deploys only after the protected Cloudflare inputs pass, and then requires the
51 public `/api/facts` receipt to report the exact workflow SHA. A credential-free
52 local comparison is available without starting a deployment:
53
54 ```bash
55 npm run compare:deployed-facts -- --expected-revision <exact-40-character-sha>
56 ```
57
58 You already own `codewhale.net` on Cloudflare and have a Workers Paid plan. The deploy is two steps:
59
60 1. **Provision KV namespaces once:**
61
62 ```bash
63 npx wrangler kv namespace create CURATED_KV
64 npx wrangler kv namespace create NEXT_INC_CACHE_KV
65 ```
66
67 Copy the printed `id` values into the matching `wrangler.jsonc` bindings
68 (replace each `REPLACE_WITH_KV_ID`).
69
70 2. **Set secrets and deploy:**
71
72 ```bash
73 npx wrangler secret put DEEPSEEK_API_KEY
74 npx wrangler secret put GITHUB_TOKEN # optional
75 npx wrangler secret put CRON_SECRET # optional, for manual /api/cron?task=curate hits
76
77 npm run deploy # builds with OpenNext + uploads
78 ```
79
80 3. **Point the domain:** in the Cloudflare dashboard, add a Worker route for `codewhale.net/*` → the deployed Worker, named `codewhale-web` (see `wrangler.jsonc`).
81
82 The first cron run happens within 6 hours; you can also kick it manually:
83
84 ```bash
85 curl -H "x-cron-secret: $CRON_SECRET" "https://codewhale.net/api/cron?task=curate"
86 ```
87
88 ## What's where
89
90 Pages are bilingual by default: each `app/[locale]/` page renders both
91 English and Chinese from the same file, keyed by the `[locale]` segment
92 (see `lib/i18n/config.ts`). Copy changes must update both locales. The
93 v0.9.2 wave adds routed **partial** locales (ja, vi, ko, ru, uk, es, pt-BR):
94 their shared chrome (nav/footer/switcher) and home-page copy live in
95 `lib/i18n/dictionaries/<code>/` (checked by `npm run check:locales`), and
96 everything else falls back to the English copy. Routing, middleware
97 detection (`lib/i18n/detect.ts`), sitemap, and hreflang all derive from the
98 one registry.
99
100 ```
101 web/
102 ├── app/
103 │ ├── globals.css ocean portal, docs layout, type, and shared surfaces
104 │ ├── [locale]/ 10 routed locales; zh has native page bodies,
105 │ │ the rest fall back to the English body
106 │ │ ├── layout.tsx root + locale layout: html shell, fonts, nav, footer
107 │ │ ├── page.tsx home — hero, ticker, proof, decides, workflow,
108 │ │ │ start, boundaries, surfaces, install band, community
109 │ │ ├── install/page.tsx per-OS install with auto-detection
110 │ │ ├── docs/page.tsx modes / tools / approval / config / mcp / providers
111 │ │ ├── faq/page.tsx frequently asked questions
112 │ │ ├── feed/page.tsx live mirror of issues + PRs
113 │ │ ├── roadmap/page.tsx shipped / underway / considered / ruled out
114 │ │ ├── contribute/page.tsx how to PR + house rules + dev loop
115 │ │ └── admin/ maintainer panel (page.tsx + admin-client.tsx)
116 │ └── api/
117 │ ├── cron/route.ts cron tasks: curate, triage, facts-drift, …
118 │ ├── facts/route.ts public source/deployment receipt
119 │ ├── github/feed/route.ts cached JSON endpoint
120 │ └── admin/ login, logout, post (MAINTAINER_TOKEN-gated)
121 ├── data/
122 │ └── latest-published-release.json manually advanced only after publication
123 ├── components/
124 │ ├── nav.tsx sticky header w/ date strip + CJK accents
125 │ ├── footer.tsx dense 5-column footer
126 │ ├── whale.tsx shared Codewhale mark
127 │ ├── ticker.tsx live wire: merges, issues, releases + handles
128 │ ├── feed-card.tsx one issue/PR card
129 │ ├── locale-switcher.tsx N-locale dropdown; partial packs grouped last
130 │ └── install-*.tsx install page blocks (binary, code block, tiles)
131 ├── lib/
132 │ ├── types.ts shared types
133 │ ├── i18n/ locale config, en/zh dictionaries
134 │ ├── github.ts REST client + relative-time formatter
135 │ ├── deepseek.ts v4-flash chat client + curate() prompt
136 │ ├── facts.ts getFacts(): KV value, else build-time FACTS
137 │ ├── facts.generated.ts GENERATED — do not edit by hand
138 │ ├── changelog.generated.ts GENERATED at build/test time, untracked
139 │ ├── install-guide.generated.ts GENERATED at build/test time, untracked
140 │ ├── facts-drift.ts runtime re-derivation for the drift cron
141 │ ├── community-agent.ts triage / pr-review / digest cron tasks
142 │ └── kv.ts Cloudflare KV access via OpenNext bindings
143 ├── scripts/
144 │ ├── derive-facts.mjs prebuild: repo sources → lib/facts.generated.ts
145 │ ├── derive-changelog.mjs prebuild + vitest setup: CHANGELOG.md → lib/changelog.generated.ts
146 │ ├── derive-install.mjs prebuild + vitest setup: docs/INSTALL.md → lib/install-guide.generated.ts
147 │ ├── compare-deployed-facts.mjs credential-free exact-SHA receipt check
148 │ └── check-kv-id.mjs predeploy guard for KV namespace ids
149 ├── wrangler.jsonc CF Worker config + cron + KV binding
150 ├── open-next.config.ts OpenNext adapter config
151 └── tailwind.config.ts design tokens
152 ```
153
154 ## Facts pipeline
155
156 Mechanical facts (version, provider list, sandbox backends, crate names,
157 default model, Node engines) are never hand-written into pages:
158
159 1. **Build time** — `scripts/derive-facts.mjs` runs as `prebuild` (and before
160 `npm run dev`), parses the parent repo (`Cargo.toml`, `crates/tui/src/config.rs`,
161 `crates/tui/src/sandbox/mod.rs`, `npm/codewhale/package.json`) and writes
162 `lib/facts.generated.ts`. Never edit that file by hand.
163 2. **Published release** — `data/latest-published-release.json` records the
164 latest GitHub Release separately from the source candidate. Install commands
165 use this published tag; they never turn the workspace version into a release
166 before publication. The credential-free deployed-facts comparison checks the
167 record against the public receipt.
168 3. **Runtime** — the `/api/cron?task=facts-drift` cron (`lib/facts-drift.ts`)
169 resolves an exact `main` revision, derives every source fact from that SHA,
170 and writes changes to `CURATED_KV` under `facts:current`. Pages accept that
171 snapshot only when its source provenance is the same as or newer than the
172 deployed build. Legacy, malformed, or older KV data cannot replace newer
173 build facts; published-release metadata is resolved independently. Public
174 fact pages revalidate their cached HTML every five minutes.
175
176 `/api/facts` exposes only public provenance and counts: deployed/resolved source
177 revision, version, provider count, tool count, selection reason, and latest
178 published release. It contains no environment values, tokens, or KV contents.
179
180 Public provider facts derive the complete roster and optional presentation
181 labels from `crates/config/assets/provider_descriptors.json`, alongside the
182 catalog from the same selected source revision. Both local generation and the
183 remote drift check validate that descriptor document and refuse missing or
184 malformed data. Add metadata there; there is no separate website label map or
185 TUI enum roster to update.
186
187 ## Visual direction
188
189 The public site is a documentation portal with a restrained underwater atmosphere. Content and navigation come first; ocean depth, currents, and the whale mark provide identity without turning every section into a themed card.
190
191 - **Palette**: cool paper and mist for reading surfaces, deep navy for terminal and community sections, muted current blue for links, and small gold/coral signals where status needs contrast.
192 - **Type**: Space Grotesk for headings, IBM Plex Sans for body copy, and JetBrains Mono for commands and compact interface labels.
193 - **Structure**: compact documentation rows, quiet hairline dividers, generous but bounded reading widths, and responsive layouts that remove chrome before content.
194
195 If you want to retune the palette, edit `:root` in `app/globals.css` and the `colors` block in `tailwind.config.ts`.
196
196 lines MARKDOWN