Skip to content

ADR-0007 Phase 0: OpenAPI → TypeScript 型生成の基盤導入#289

Merged
yusuke0610 merged 4 commits into
mainfrom
feat/openAPI-typescript
May 29, 2026
Merged

ADR-0007 Phase 0: OpenAPI → TypeScript 型生成の基盤導入#289
yusuke0610 merged 4 commits into
mainfrom
feat/openAPI-typescript

Conversation

@yusuke0610

@yusuke0610 yusuke0610 commented May 29, 2026

Copy link
Copy Markdown
Owner

概要

ADR-0007(OpenAPI → TypeScript 型コード生成の導入・完全移行)Phase 0(前提・基盤) を実装する。

backend(Pydantic schema)と frontend(手書き TypeScript 型)の DTO 二重定義は、これまで目視同期に依存しており、機械的なドリフト検知の仕組みが無かった(jscpd は .py / .ts を別トークナイザで扱うため cross-realm clone を検出できない)。

この PR では型移行そのものは行わず、OpenAPI から TypeScript 型を自動生成するパイプラインと CI ドリフト検知を整備する。あわせて、完全移行の前提となる response_model 未設定エンドポイントの schema 化を行う。

このPRは基盤整備のみ。frontend の手書き DTO 型の生成物への移行は Phase 1 以降(別 PR)で実施する。

背景

  • backend が app/schemas/** で定義する DTO を、frontend が types.ts / api/*.ts に手書きで再定義しており、フィールド構造を人手で同期している。
  • backend でフィールド追加・rename・型変更を行っても frontend 側は型エラーにならず、ランタイムで undefined を踏むまで気づけない。
  • 対照的にエラーコードは Record<ErrorCodeKey, ...> の型縛りでビルド時に漏れを検知できている。DTO にも同等の「漏れたらビルドで落ちる」仕組みを与えたい。
  • OpenAPI に型が出るのは response_model 付きエンドポイントのみ(ADR 起票時の実測 40 中 26)。完全移行の前提として、DTO を持つのに response_model 未設定のエンドポイントを Phase 0 で schema 化・付与する。

変更内容

1. パイプライン構築

ファイル 内容
backend/scripts/export_openapi.py(新規) FastAPI app の app.openapi()backend/openapi.json にダンプ。ENVIRONMENT 未設定時は local 扱いとし import 時の設定チェックを回避。diff 安定化のため sort_keys=True で出力。
frontend/scripts/gen-types.mjs(新規) backend/openapi.jsonfrontend/src/api/generated.tsopenapi-typescript で生成。冒頭に「手編集禁止」バナーを付与。
Makefile make codegen-types を追加(Nix devshell 経由で export → 型生成を一括実行)。
frontend/package.json openapi-typescript@^7.13.0 を devDependency に追加。
frontend/src/api/generated.ts(新規・生成物) 初回生成。手編集禁止。コミット対象。

2. 生成物の lint / format 除外

  • frontend/eslint.config.js: src/api/generated.ts を ESLint 対象から除外(生成スタイルとの衝突回避)。
  • frontend/.prettierignore(新規): 同ファイルを Prettier 対象外に。
  • .gitignore: 中間生成物 backend/openapi.json を除外(コミット対象は generated.ts のみ)。

3. response_model 未設定エンドポイントの整備(完全移行の前提)

匿名 dict を返していたエンドポイントに schema を付与し、OpenAPI に型が出るようにする。

エンドポイント 変更
GET /auth/github/login-url 新規 GitHubLoginUrlResponseauthorization_url / state)を追加し response_model 付与。dict[str, str] の返却をやめる。
POST /github/run
POST /github/run/retry
202 受付応答を共通 TaskAcceptedResponsestatus)に集約して DRY 化。{"status": ...} の dict 返却をやめる。

新規 schema: backend/app/schemas/auth.pyGitHubLoginUrlResponsebackend/app/schemas/shared.pyTaskAcceptedResponsebackend/app/schemas/__init__.py でエクスポート。

4. CI ドリフト検知

.github/workflows/ci.ymlcodegen-drift ジョブを追加。app 変更時に走り、make codegen-types 相当(OpenAPI export → 型生成)を実行後、git diff --exit-code frontend/src/api/generated.ts で差分があれば fail。「backend schema を変えたのに型を再生成していない」状態をビルドで落とす。

移行しないもの(この PR の対象外)

  • frontend 手書き DTO 型の生成物への移行(Phase 1 以降)
  • api/client.ts の 401/CSRF/Cookie 認証ロジック
  • api/paths.tsPATHS(API パス SSoT として維持)
  • constants/errorCodes.ts / errorMessages.ts(別系統の型縛り)

動作確認

# 型再生成(生成物に差分が出ないこと)
make codegen-types
git diff --exit-code frontend/src/api/generated.ts

# CI 相当
make ci

ADR への影響

ADR-0007 のステータスは引き続き Proposed。Phase 0 + Phase 1 パイロットが make ci green を満たし、CI ドリフト検知が機能する(backend schema をわざと変えると CI が落ちる)ことを確認できた時点で Accepted に昇格する。

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced API response contracts for authentication and background task handling with proper type definitions.
    • Automated code generation pipeline to synchronize frontend types with backend API schema.
  • CI/CD

    • Added validation checks in continuous integration to detect and prevent type inconsistencies between backend and frontend definitions.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@yusuke0610, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 40 minutes and 13 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 20d948db-c502-4f1d-a21e-07e886842854

📥 Commits

Reviewing files that changed from the base of the PR and between edea51b and 783bcce.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • backend/scripts/export_openapi.py
📝 Walkthrough

Walkthrough

This PR implements an OpenAPI type-generation pipeline that extracts the backend FastAPI schema and auto-generates TypeScript type definitions for the frontend, with CI enforcement to prevent schema-generated-types drift. Backend endpoints are updated to use structured response schemas, enabling accurate OpenAPI export and downstream type generation.

Changes

OpenAPI Type Code-Generation Pipeline

Layer / File(s) Summary
Backend response schema contracts
backend/app/schemas/auth.py, backend/app/schemas/shared.py, backend/app/schemas/__init__.py
Pydantic models GitHubLoginUrlResponse and TaskAcceptedResponse define the contract for typed HTTP responses; both are exported from the schemas module.
Backend endpoints adopt typed responses
backend/app/routers/auth/endpoints.py, backend/app/routers/github_link.py
Auth and GitHub-link endpoints declare response_model and return instances of the new typed schemas instead of raw dictionaries.
OpenAPI schema extraction and Make target
backend/scripts/export_openapi.py, .gitignore, Makefile
Python script loads the FastAPI app and writes its OpenAPI schema to JSON; Makefile and .gitignore coordinate the codegen-types workflow with intermediate artifact management.
Frontend type generation setup
frontend/package.json, frontend/scripts/gen-types.mjs, frontend/.prettierignore, frontend/eslint.config.js
Node.js script reads the OpenAPI schema and generates TypeScript types; dependencies, Prettier, and ESLint are configured to exclude the auto-generated output from manual edits and style enforcement.
Generated TypeScript API type definitions
frontend/src/api/generated.ts
Auto-generated file exports OpenAPI contracts as TypeScript: paths (endpoint signatures), components (request/response DTOs), and operations (per-operationId request/response typing).
CI drift detection job
.github/workflows/ci.yml
GitHub Actions job regenerates the OpenAPI schema and TypeScript types, then fails if the committed frontend/src/api/generated.ts differs from freshly generated output.

🐰 A schema born from code so fine,
Now types align with back and front!
Export→Generate→Drift-detect,
In harmony, the types are correct!

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: introducing foundational infrastructure for OpenAPI-to-TypeScript type generation (ADR-0007 Phase 0), which is the core purpose of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/openAPI-typescript

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/schemas/__init__.py (1)

38-38: ⚡ Quick win

TaskAcceptedResponse is not re-exported here.

TaskStatusResponse (and GitHubLoginUrlResponse) are exported through this package, but the new TaskAcceptedResponse is omitted from both the .shared import and __all__. github_link.py imports it directly from ..schemas.shared so nothing breaks today, but this is inconsistent with the sibling schemas and the PR's stated intent to export new schemas.

♻️ Proposed fix
-from .shared import TaskStatusResponse
+from .shared import TaskAcceptedResponse, TaskStatusResponse

Add to __all__:

+    "TaskAcceptedResponse",
     "TaskStatusResponse",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/schemas/__init__.py` at line 38, Add the new TaskAcceptedResponse
to the package re-exports so it is consistently available like
TaskStatusResponse and GitHubLoginUrlResponse: update the import line in
__init__.py to include TaskAcceptedResponse (from .shared import
TaskStatusResponse, TaskAcceptedResponse, GitHubLoginUrlResponse) and add
"TaskAcceptedResponse" to the module's __all__ list so callers can import it
from the package rather than from ..schemas.shared.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 225-229: The new CI job "codegen-drift" must be made a hard
prerequisite for all deployment jobs so failed drift checks block deploys;
locate any jobs whose purpose is deployment (job IDs like deploy,
deploy-staging, deploy-prod or any job that currently needs: detect-changes) and
add "codegen-drift" to their needs array (so needs: [detect-changes,
codegen-drift] or include it alongside other upstreams), ensuring the deploy
jobs' if conditions still reference needs.detect-changes.outputs as needed and
that the job id "codegen-drift" matches exactly.
- Around line 232-257: Replace the floating action tags with pinned commit SHAs
and disable credential persistence on checkout: change uses: actions/checkout@v4
to uses: actions/checkout@<commit-sha> and add persist-credentials: false,
replace uses: astral-sh/setup-uv@v4 and uses: actions/setup-node@v4 with their
respective commit SHAs (e.g. astral-sh/setup-uv@<commit-sha>,
actions/setup-node@<commit-sha>) so the workflow references fixed revisions and
does not expose repository tokens.

---

Nitpick comments:
In `@backend/app/schemas/__init__.py`:
- Line 38: Add the new TaskAcceptedResponse to the package re-exports so it is
consistently available like TaskStatusResponse and GitHubLoginUrlResponse:
update the import line in __init__.py to include TaskAcceptedResponse (from
.shared import TaskStatusResponse, TaskAcceptedResponse, GitHubLoginUrlResponse)
and add "TaskAcceptedResponse" to the module's __all__ list so callers can
import it from the package rather than from ..schemas.shared.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c67345d0-ebdf-4eba-b47b-2edd843b323c

📥 Commits

Reviewing files that changed from the base of the PR and between 176380c and edea51b.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • .github/workflows/ci.yml
  • .gitignore
  • Makefile
  • backend/app/routers/auth/endpoints.py
  • backend/app/routers/github_link.py
  • backend/app/schemas/__init__.py
  • backend/app/schemas/auth.py
  • backend/app/schemas/shared.py
  • backend/scripts/export_openapi.py
  • frontend/.prettierignore
  • frontend/eslint.config.js
  • frontend/package.json
  • frontend/scripts/gen-types.mjs
  • frontend/src/api/generated.ts

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml Outdated
@yusuke0610

Copy link
Copy Markdown
Owner Author

まとめ

CI 失敗の原因と修正:

  • app.main の import 時に app/db/database.py のモジュールレベルで build_sqlalchemy_database_url() が走り、TURSO_DATABASE_URL を必須要求していた。ローカル devshell
    には設定済みだが CI の codegen-drift には無く落ちていた。
  • export_openapi.py に os.environ.setdefault(env_keys.TURSO_DATABASE_URL, "file:openapi-export-dummy.sqlite") を追加。あわせて既存の ENVIRONMENT リテラルも
    env_keys.ENVIRONMENT 経由に統一し、sys.path.insert を env_keys import の前へ移動(cwd 非依存化、# noqa: E402)。

検証:

  • CI 環境を再現(env -u TURSO_DATABASE_URL で実行)→ import が通り OpenAPI 出力成功。
  • make codegen-types フル実行 → git diff --exit-code generated.ts で drift なし。コミット済みの生成物と一致。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant