開発環境
- Node.js 24 (
.nvmrcのバージョン)。nvm があればnvm use。 - pnpm 11 —
packageManagerが固定している版。corepack enableで入る。 - Apple Container と container-compose (どちらも Homebrew)— スタックに必要。ホストだけの開発には不要。macOS 専用です。
curlとgitが PATH にあること。DATABASE_URLで届く Postgres。Compose スタックが立ち上げます。- 実際に取り込むには BrowserHive と、それを回す Windmill の flow。capture-ledger は
もう BrowserHive の在り処を持たず、
CAPTURE_LEDGER_CRAWL_WEBHOOK_URLへ投げるだけです。 flow は capture-scheduler に居ます。スタックが今も BrowserHive を build するのは flow が要るからで、固定バージョンは BrowserHive の更新を参照。
初回セットアップ
Section titled “初回セットアップ”git clone https://github.com/<you>/capture-ledger.gitcd capture-ledgernvm usepnpm installsudo container system dns create capture-ledger # マシンごとに 1 回git submodule update --init --recursive # 上流のソースを .upstream/ にcp -n .env.example .env # 設定の雛形をそのまま写すpnpm run check # push の前に走らせる検査の全部(中身は package.json の check)スタックは container-compose を直接叩かず、pnpm run stack:up で起動してください。
起動の前に、道具と capture-ledger DNS ドメイン、すべての build context が指す
.upstream/ の submodule、そして共有 store が起きているかを確かめ、足りなければ
名前を挙げて止まります。
成果物を置く store(SeaweedFS)は、このスタックには入っていません。crawler の 3 つの repo が
共有する 1 つの store(seaweedfs.crawler-storage)で、submodule から起こします:
sudo container system dns create crawler-storage # マシンごとに 1 度だけsh .upstream/seaweedfs/scripts/stack.sh up中身を消す・見る・store ごと作り直す手順は、
seaweedfs の運用ガイド
に 1 つだけ置いてあります(この repo の bucket を空にするだけなら pnpm run store:wipe)。
コードが読む環境変数は、読み取りの仕組みが 3 つに分かれています。
src/config/ の required()/optional()、commander の .env()(こちらは
--help にも出ます)、そして素の process.env[…](これは scripts/ にもあります)。
数は pnpm run check-env が数えて出します。必須のものは src/config/env.ts の REQUIRED_ENV に並んでいます。
一覧は .env.example の 1 か所だけで、.env はその写しです(cp -n .env.example .env)。
値を調べて .env を作るものはありません —— 一覧が 2 つあれば必ずずれるからです。
OpenFGA の 2 つの ID は、走らせてみないと決まらないので雛形には名前しかありません
(pnpm run fga:deploy が .env.local に書きます。
アーカイブ台帳を参照)。
scripts/check-env.mjs(pnpm run check に含まれ、CI では独立したステップ)が、
コードの読み取りと .env.example の宣言を両方向で突き合わせます。古い雛形は
雛形が無いより悪い —— 信用して使われるので、足りないときに疑う先が残りません。
設定のファイルは 2 枚ある
Section titled “設定のファイルは 2 枚ある”実行系の script は .env と .env.local をこの順で node に渡します
(--env-file-if-exists を 2 つ)。後に渡したほうが勝つので、同じ名前が両方に
在れば .env.local の値が効きます。
| ファイル | 持ち主 | 中身 |
|---|---|---|
.env |
人 | .env.example の写し。手で直すのはこちらだけ |
.env.local |
道具 | 走らせてみないと決まらない値。git は無視する(生成物) |
.env.local に書く道具は 2 つです。pnpm run fga:deploy(OpenFGA の store id と
model id)と、pnpm run connect(capture-scheduler が渡す 4 行 —— クイックスタート §7)。
分けてあるのは持ち主が違うからで、道具が人の書いた行を並べ替えたり消したりすると、
次に何が起きたのか追えなくなります。
どちらが勝っているかは、API の起動ログの 1 行目が言います:
config: .env (16 names), .env.local (6 names) — .env.local wins for CAPTURE_LEDGER_FGA_STORE_ID, … (a value set in the shell beats the files)「.env を直したのに効かない」ときは、この行に名前が出ていないかを見ます。出ていれば
.env.local が勝っています。古い .env.local を疑うなら 作り直すのがいちばん速い ——
fga:deploy も connect も、何度打っても構いません。
この行が出すのは名前だけで、値は出しません。token が混じるうえ、出どころの説明に 値は要らないからです。端末で渡した値は、どちらのファイルにも勝ちます。
空文字は「値が無い」とは別の状態
Section titled “空文字は「値が無い」とは別の状態”.env に FOO= と書くと FOO は空文字になります。行ごと無ければ未設定です。
この 2 つは別の状態で、POSIX はそれぞれに別の記法を与えています ——
${FOO:-default} は両方で既定値に落ち、${FOO-default} は未設定のときだけです。
この repo は前者に揃えてあります。空文字は「無い」と同じ意味です。env は
optional()(または need())で読み、process.env[…] ?? default は使いません
—— それは後者の意味で、空文字をそのまま値にしてしまいます。
そして「空で設定されている」はほぼ必ず打ち間違いなので、src/config/env.ts の
起動時検査は黙って既定値に戻さず、名前を挙げて止まります。これには理由があって、
空文字は「無い」より厳密に悪い状態でした —— DATABASE_URL= は commander の必須
チェックを通り、API は起動し、/healthz は 200 を返し、最初のクエリで
DATABASE_URL という語を一度も出さない SASL エラーになっていました。
そのため .env.example の行は 3 種類しかありません。
NAME=value # 値を渡す#NAME=value # 既定値を見せるだけ(使うならコメントを外して値を書く)#NAME= # 起動前には値が決まらないもの(道具が .env.local に書く)生の NAME= を書けるのは必須のもの(REQUIRED_ENV)だけです。必須の空は collectEnv が
「不足」として報告します。この規則も check-env.mjs が検査しています。
日々のコマンド
Section titled “日々のコマンド”| コマンド | 内容 |
|---|---|
pnpm run dev:up |
立ち上げの段を順に。--dry-run で一覧、--from <段> で途中から。 |
pnpm run dev:status |
いま何が立っているか。何も変えない。 |
pnpm run dev:down |
ホストで動かしたもの → 両 repo のコンテナ。共有 store は落とさない。 |
pnpm run api |
ビルドしてから API を実行 (tsc → node dist/api/server.js)。 |
pnpm run build |
tsconfig.build.json で dist/ に JS/d.ts を出力。 |
pnpm run typecheck |
tsc --noEmit。テストと *.config.ts も含む。 |
pnpm run lint / lint:fix |
ESLint flat config (typescript-eslint recommendedTypeChecked)。 |
pnpm run format / format:check |
Prettier。.prettierignore が dist/ と src/rpc/generated/ を除外。 |
pnpm test / test:watch |
test/ 配下の Vitest ユニットテスト。 |
pnpm run audit |
既知の脆弱性 (pnpm-lock.yaml)。CI では毎日も走る。 |
pnpm run check |
push の前に走らせる検査の全部。中身と並びは package.json の check(CI の check job と同じ)。 |
pnpm run db:migrate / db:migrate:down |
DATABASE_URL に対する Kysely マイグレーション。 |
pnpm run db:seed / db:seed:down |
src/db/seeds/ の seed。 |
pnpm run targets |
撮る対象(capture_targets)を足す・見る・外す・消す。DB に直接書く開発用の道具。 |
pnpm run scripts |
ページで走らせるスクリプト(scripts)を取り込む・足す・見る・読む・外す・消す。受け皿の型(host_types)も出す。DB に直接書く。 |
pnpm run openapi:generate |
固定した submodule から BrowserHive の openapi.json を取り直し、型を再生成 (openapi-typescript)。 |
pnpm run openapi:check |
型を再生成して git diff --exit-code (CI のドリフト検査)。 |
pnpm run openapi:sync |
固定した submodule から openapi.json を取り直すだけ。 |
pnpm run site:dev / site:build |
このドキュメントサイト。 |
pnpm run site:check |
サイトをビルドし、参照の整合を検証。 |
pnpm run docs:shots |
docs に載せる画面の絵を撮り直す (スタックは要らない)。 |
ホストで動くもの
Section titled “ホストで動くもの”スタックに入っていないものがあります。pnpm run oidc:issuer (9099)・
pnpm run api (7070)・wacz-validator の
daemon (7180)・dashboard の
pnpm run dev (7080) はホストのプロセスで、コンテナではありません。
検証の daemon には store の鍵を渡していません。読むのは台帳が署名した URL
だけなので要らず、渡せば「鍵を持つ相手を増やさない」という選択の意味が消えます。
daemon の既定の port は 0(OS 任せ)なので、--port 7180 で固定してあります ——
固定しないと下の 2 つが見つけられません(どちらも port から持ち主を引くため)。
daemon は 自分で建て直りません。wacz-validator を出しても、7180 は起こした時の
build のまま動き続けます(2026-09-26 に見たとき、v0.28.1 が 2 日動いていて、その間に
出た v0.29.0・v0.30.0 の rule はどれも dashboard に出ていませんでした)。pnpm run dev:status が、
動いている build(/healthz が名乗る commit)を wacz-validator の checkout の HEAD と
比べます。違えば、checkout が先に進んだのか(古い build)、それ以外か(違う build)を言い、
起こし直す 1 行を添えます。
dashboard が 7000 ではなく 7080 なのは、macOS の ControlCenter (AirPlay Receiver) が
*:7000 を握っているためです(実測)。127.0.0.1:7000 に bind できてしまうので
気づきにくいのですが、port から持ち主を引く下の道具が毎回「別のものが握っている」と
言うことになります。
だから container-compose down では消えません。2026-09-20 に
EADDRINUSE: address already in use 127.0.0.1:9099 で止まったのはこれで、前の日に
別のターミナルで起こした issuer が残っていました。スタックを落としたのに港が
塞がっている、という形で出ます。
pnpm run dev:status # pid・起動時刻・port。validator は動いている build も。何も変えないpnpm run dev:down # ホストで動かしたもの → 両 repo のコンテナdev:down は port を持っている相手が自分のものか を先に確かめます —— コマンド行が
この repo の entry を動かしていて、かつプロセスの cwd がこの repo の根であること。
違えば 止めずに名指しします(番号は使い回されるので、port から引いた PID を
そのまま kill してはいけません)。
スタックで作業する
Section titled “スタックで作業する”pnpm run stack:up# 準備完了の判定は GET /status (BrowserHive の HTTP API)。# browserhive-2 も同じ (localhost:50052)。until curl -fsS http://localhost:50051/status >/dev/null 2>&1; do sleep 1; donecapture-ledger のコマンドは、この repo の端末でそのまま打ちます。
capture-ledger はホストで動き、
スタックが 127.0.0.1 に publish した口を通してコンテナ側に繋がります。
接続文字列は雛形に入っているので、写した .env にそのまま在ります。
DATABASE_URL=postgres://capture_ledger:capture_ledger@127.0.0.1:5432/capture_ledgerBrowserHive の在り処はもうここにありません。スタックが公開している 2 つの HTTP の
口 localhost:50051 と localhost:50052(Chromium 1 台に BrowserHive 1 つ)は、
curl と flow のためのもので、capture-ledger のためではありません。
pnpm run 系のコマンドはこの .env を自分で読みます
(node --env-file-if-exists=.env)。シェルで export する必要はありません。
すでに環境にある変数のほうが優先されるので、一時的に別の DB を向きたいときは
DATABASE_URL=... pnpm run db:migrate と前置きすれば効きます。node dist/... を
直接叩く場合は読まれないので、そのときは自分で渡してください。
Postgres は 127.0.0.1:5432 にも公開しているので localhost でも繋がります。
Docker Compose から来た場合、日常のコマンドはこう対応します。
| Docker Compose | このスタックでは |
|---|---|
docker compose up -d --build |
pnpm run stack:up |
docker compose down |
pnpm run stack:down |
docker compose ps |
container ls |
docker compose logs browserhive-1 |
container logs browserhive-1.capture-ledger |
docker compose exec <svc> sh |
container exec -it <svc>.capture-ledger sh |
docker compose run --rm <svc> … |
container run --rm <image> … |
起動と停止だけ container-compose ではないのは、scripts/stack.sh が渡すものが 3 つあるから
です —— 署名の profile と env ファイルの対、submodule の版(渡らないで建った image は
BrowserHive が起動を拒みます)、そして起動の前の点検です。
Chromium は 2 台とも headless です。描画を見たいときは、ローカルの Chrome で
chrome://inspect を開き、Configure… に localhost:9222 と localhost:9223
を登録してターゲットを inspect します。
本番イメージのスモークテスト
Section titled “本番イメージのスモークテスト”./scripts/prod-smoke.shスタックを起動し、BrowserHive が 2 つとも GET /status に応答するまでポーリングし、
capture-ledger:latest をビルドしてから migrate → seed → API を container run --rm で
順に実行し、API に /healthz を訊き、EXIT トラップでスタックを片付け、
終了コードを自分の終了コードとして返します。
もう取り込みはしません。 capture-ledger は BrowserHive と話さないので、
このスクリプトが示すのは「イメージが起動すること」—— migration が当たり、seed が
入り、API が答えること —— です。取り込みの経路は capture-scheduler の pnpm run test:e2e が
端から端まで見ます(あちらは Windmill も要ります)。
一発ジョブが素の container run なのは、container-compose に run が無いから
です。これにより、以前の --profile run --exit-code-from capture-ledger の回避策も
不要になりました — 回避対象だった Docker Compose の挙動(migrator の正当な
exit 0 でスタック全体が停止する)が、こちらには存在しないためです。
外部の Postgres に対して動かす
Section titled “外部の Postgres に対して動かす”DATABASE_URL=postgres://user:pass@db.host:5432/capture_ledger \ pnpm run db:migrate
DATABASE_URL=postgres://user:pass@db.host:5432/capture_ledger \CAPTURE_LEDGER_CRAWL_WEBHOOK_URL=https://windmill.example/api/w/…/jobs/run/f/f/waggle/crawl_level \CAPTURE_LEDGER_CRAWL_WEBHOOK_TOKEN=… \ pnpm run apiwebhook の URL の … は Windmill の workspace の id です(同梱の構成なら crawler)。
末尾の f/waggle/crawl_level は flow のパスで、決めているのは capture-scheduler です。
名前が違っていても API は起動し、POST /api/crawls も 202 を返します。
分かるのはその後で、クロールが failed になり、
API のログに crawl webhook → 404 Not found: flow not found が出ます。
Postgres の TLS は DATABASE_URL にパラメータを書きます (例: ?sslmode=require)。
BrowserHive の TLS はもうここで設定しません。 そのチャンネルを持っているのは
flow なので、CA は Windmill 側の変数 u/admin/browserhive_tls_ca に置きます
(空文字なら平文)。
ローカルで身元を用意する
Section titled “ローカルで身元を用意する”身元の入口は API の 1 つだけで、既定では全員を拒みます。
| 経路 | 既定 | 開発用ヘッダ | JWT |
|---|---|---|---|
API (/api, picker) |
拒否 | CAPTURE_LEDGER_DEV_IDENTITY=1 |
CAPTURE_LEDGER_OIDC_ISSUER |
以前は CLI の行がもう 1 つあり、環境から CAPTURE_LEDGER_DEV_SUBJECT と
CAPTURE_LEDGER_OIDC_TOKEN を読んでいました。その 2 つは、読む側の CLI ごと畳んだときに
.env.example からも消えています。主体を名乗るのは投げる側の仕事になりました。
JWT の経路が開発用ヘッダより優先されます。 両方設定された環境で、 そのポートに届く者が誰にでもなれるほうへ落ちてはいけないためです。 picker は、API がどちらで起きたかに合わせて欄を変えます —— 開発用ヘッダなら名乗りの 2 欄、 JWT ならトークンの欄です(アーカイブを見る)。
開発用の issuer
Section titled “開発用の issuer”CAPTURE_LEDGER_OIDC_ISSUER を設定すると、API は 本番と同じ検証コード を通ります
—— 署名、iss / aud の照合、有効期限、JWKS の取得。本物の IdP が決まるまでは
同梱の issuer を使います。
pnpm run oidc:issuer # :9099 に立つexport CAPTURE_LEDGER_OIDC_ISSUER=http://127.0.0.1:9099TOKEN=$(pnpm run oidc:token --subject alice --org acme)curl -H "authorization: Bearer $TOKEN" http://127.0.0.1:7070/api/crawlsトークンは .env に置きません —— 読むのは受け取る側の API で、投げる側が
Authorization ヘッダに載せるものだからです。
--subject を変えると 「人が投げた場合」と「サービスが投げた場合」の両方を
作れます。後者は OpenFGA の owner tuple が user:<サービス名> になり、
そのアーカイブを人が消せなくなる状態です —— 認証を入れるより前に、認可の設計を
ここで踏めます。
本物の IdP へ移るとき
Section titled “本物の IdP へ移るとき”変わるのは CAPTURE_LEDGER_OIDC_ISSUER と CAPTURE_LEDGER_OIDC_AUDIENCE の値だけです。
jwtIdentityResolver は 1 行も変わりません。
ただし JWKS を HTTP で取ってくる経路は単体試験では守れません。
createRemoteJWKSet をローカルの鍵に差し替えても試験は緑のままなので、
この節の手順を実際に通すことがその代わりになります。
組織のクレームの綴りは IdP ごとに違います (groups / roles / 独自)。
差し替えるのは src/config/identity.ts の ORGANIZATIONS_CLAIM 1 か所だけです ——
API は identityFromClaims を通してそこを読みます。
起動ログで確かめる
Section titled “起動ログで確かめる”API は起動したときに、最後の 1 行で待ち受けのアドレス・名乗り方・クロールの段の報告を
受けられるかを言います(src/api/startup-notes.ts)。
Archive API listening on 0.0.0.0:7070 — identity: JWT (http://127.0.0.1:9099); crawl level reports: readycrawl level reports |
意味 |
|---|---|
ready |
段の報告を受けられる —— コンテナから届くアドレスで待ち、JWT を受ける |
blocked |
受けられない。その上の warn が、足りない行を名指しする(CAPTURE_LEDGER_API_HOST=0.0.0.0 と CAPTURE_LEDGER_OIDC_ISSUER)。クロールは running のまま残る |
off |
クロールの口が無い(webhook の 2 行が無い) |
warn はほかに 3 つあります。名乗りの設定がどちらも無い(全員 401)、開発用ヘッダを信じる API を
127.0.0.1 以外で待たせている(そのポートに届く誰もが、誰にでもなれる。picker も同じ判定で、
画面の上に赤い注意を出します)、受け口が在るのに
127.0.0.1 で待っている(BrowserHive のコンテナから PUT が届かない)。CAPTURE_LEDGER_DEV_IDENTITY=1
の警告は、ヘッダが実際に効いているときだけ出ます —— JWT の設定では「無視される」と info で言います。
トラブルシュート
Section titled “トラブルシュート”- コンテナが上がらない —
container lsで起動状況、container logs <svc>.capture-ledgerで理由を見る。Chromium ならcurl http://localhost:9222/json/versionで CDP の応答を確認。 - 名前が解決しない —
container system dns lsにcapture-ledgerがあるか、docker-compose.ymlのどのサービスにもcontainer_name:が無いか (付けると DNS 命名が抑止される)を確認。 - BrowserHive が起動直後に落ちる — 起動時の
HeadBucketは fatal です。 サービスにWAIT_FOR_S3が設定されているか、SeaweedFS がBucket browserhive ready.を出しているか確認してください。 /api/crawlsが誰に対しても 404 を返す — 呼び出し元にcan_submitが 無いか、CAPTURE_LEDGER_CRAWL_WEBHOOK_URLが未設定で route がそもそも登録されて いないかのどちらかです。どちらかは起動時のログが言います (… is not set — /api/crawls is not served)。- クロールが
runningのまま残る(段の報告が届かない・401) — API の起動ログの最後の行がcrawl level reports: blockedなら、その上の warn が足りない行を名指しします (起動ログで確かめる)。 - docs のビルドが BrowserHive のピンを読めない —
git submodule update --init --recursiveを実行。
自分の持ち物をクロールする
Section titled “自分の持ち物をクロールする”スタックには取り込み対象のフィクスチャ fixtures が profile 付きで入っている。見知らぬサイトに向けずにクロールを試せる:
pnpm run stack:up --profile capture-fixturesport は publish していない。capture-fixtures.capture-ledger:8080 はコンテナからも host からも引ける。
起点にするのは /links/hub —— /links/* はどれもそのページを 1 か所だけ変えたもので、
それが「誤った規則を適用しているクローラ」と「単に壊れているクローラ」を分ける。
fixtures はリクエストログも持っていて、それを使うことがこのフィクスチャの要点:
curl -s http://capture-fixtures.capture-ledger:8080/__request-countscrawl_pages が言うのは capture-ledger が記録したこと、ログが言うのはフィクスチャが
実際に要求されたこと。「robots を尊重した」は後者でしか決着しない —— 台帳に無い
ページは、取りに行かなかったのか、取りに行って捨てたのか、台帳からは区別できない。
fixtures は .upstream/browserhive 経由ではなく .upstream/fixtures に直接 vendor して
いる。あちらが抱える fixtures はずっと古く、2 つの pin は別々の都合で動くので、
どちらももう一方を待つ理由が無い。
署名には 2 つのことが同時に要ります —— wacz-signer と tsa が起きていること、
BrowserHive が署名の宛先を知っていること。1 行で両方が立ちます。
CAPTURE_LEDGER_CAPTURE_SIGNING=1pnpm run stack:up がこの行を読み、--profile signing(wacz-signer と tsa を起こす)と
--env-file signing.env(BrowserHive に宛先を渡す)を足します。何を足したかを印字するので、
wacz-signer が起きている理由が分からなくなることはありません。
**片方だけを渡す道はありません。**そこが眼目で、以前は設定が互いを知らない 3 か所に
分かれていました —— .env の旗、profile、そして docker-compose.yml に直書きされた
BROWSERHIVE_SIGNING_* の 4 つ。profile を起こさずに署名を on にすると、取り込みが全部
ENOTFOUND wacz-signer.capture-ledger で落ちました。DNS の誤りに見えますが、そうではありません ——
名前は正しく、サービスが起きていなかっただけです。
署名の設定を docker-compose.yml へ戻すことはできません。空にしても駄目です。これは
空文字の罠がもう一段外側で再来したもので、BrowserHive の
分岐は signing.url === undefined なのに、commander は envVar in process.env で
判定するため - BROWSERHIVE_SIGNING_URL= は設定済みとして通ります。結果は
fetch("") と TypeError: Failed to parse URL from —— 上の DATABASE_URL= の話と
同じで、変数の名前を一度も出さない誤りになります。項目そのものが無ければ
BrowserHive は no signing service is configured on this server と言います。
署名は fail-closed です —— 署名を求めて得られなかった取り込みは、署名なしのアーカイブを 出すのではなく失敗します。
受け口を通す
Section titled “受け口を通す”既定では、BrowserHive は成果物を自前の bucket へ書きます。受け口はもう 1 つの経路で、
capture-ledger がクロールごとに 1 回きりの URL とトークンを配り、BrowserHive は成果物を
capture-ledger へ PUT します(src/api/sink.ts)。BrowserHive が全テナントに書ける鍵を
持たずに済むための口です。2 つの変数で立ち、2 つ揃ったときだけです —— 片方だけだと
API は起動しません。
CAPTURE_LEDGER_API_HOST=0.0.0.0 \CAPTURE_LEDGER_SINK_ORIGIN=http://$(container network inspect default | jq -r '.[0].status.ipv4Gateway'):7070 \CAPTURE_LEDGER_SINK_SECRET=$(openssl rand -hex 32) \ pnpm run apiコマンド行で渡せば .env を触らずに済みます —— Node の --env-file-if-exists は、shell が
既に持っている変数を上書きしません。
origin は BrowserHive のコンテナから API に届くアドレスで、手元から使うアドレスではありません。
dev のスタックでは、コンテナのネットワークから見た host —— default ネットワークの gateway で、
上のコマンドがその場で読みます(この文書を書いた Mac では 192.168.66.1。network を作り直すと
変わるので、写さないでください)。API は
0.0.0.0 で待ち受ける必要があります —— loopback に bind すると、コンテナからの接続を断ります。
origin を誤ると、取り込みは受け口の URL で始まる誤り(http://…/api/sink/…: …)で落ちます。
**そのうえで、成果物がどこに置かれたかを見てください。**この経路は、音も無く素通りされることも
あります。受け口を立てて始めたクロールは crawls.artifact_key_prefix(org/<orgId>/<YYYY-MM>/)
を記録し、そのアーカイブの object_key はこれで始まるはずです。平らな
<taskId>_<crawlId>.wacz は BrowserHive が自前の bucket へ書いた印で、クロールは成功し、
ほかに何も教えてくれません。受け口が足されてから一度も使われなかったのはこのためです ——
src/crawl/dispatch.ts の webhook の本文は手で書き並べられていて、artifact_sink を一度も
運んでいませんでした。いまは型が網羅を保つ対応表から組み立てます。capture-scheduler の e2e も、
API に受け口の有無を訊き(トークン無しの PUT が、在れば 401、無ければ 404)、在ればその
クロールのアーカイブが全部接頭辞の下にあることを求めます。
画面の絵を撮り直す
Section titled “画面の絵を撮り直す”アーカイブを見るの絵は、scripts/docs-shots.mjs が撮った生成物です。
手で撮った絵は 1 枚もありません。絵は画面の書き写しで、書き写しは画面を直した日から古くなるためです。
./node_modules/.bin/puppeteer browsers install chrome # 初回だけ (撮影用の Chrome を取得)pnpm run docs:shotsスタックは要りません。script が立てるのは、本物の画面(src/api/picker.ts)と、
見本データを返す代役の API(/api/archives と /api/me)だけです。画面は API の名乗り方で
変わるので、名乗り方ごとに 1 組ずつ立てます(開発用ヘッダ・JWT・名乗りの設定なし・
0.0.0.0 で待つ開発用ヘッダ)。見た目(ライトモード)・言語・時間帯・見本データの日時を
固定してあるので、誰が撮っても同じ絵になります。何も変えずに撮り直しても、差分は出ません。
src/api/picker.ts を変えたら、撮り直してください。 撮ったときのソースの sha256 を
docs-site/src/assets/picker/shots-manifest.json に控えてあり、いまのソースと違うと
pnpm run site:check が落ちます。コメントだけの変更でも同じです。manifest は script が書くので、
手では直さないでください。
リポジトリの約束
Section titled “リポジトリの約束”- ソースは
src/、テストはtest/、1 モジュール 1 関心。 src/rpc/generated/は生成物でコミット対象。手で編集しない。- Prettier と ESLint が正。push 前に
pnpm run check。 - ドキュメントは
docs-site/に英語と日本語で置く。英語ページだけを追加するとpnpm run site:checkが落ちます。