Claude Media
Claude CodeでRedocly CLIのbundleとlintを回す — 分割したOpenAPIの運用設定

Claude CodeでRedocly CLIのbundleとlintを回す — 分割したOpenAPIの運用設定

$refで分割したOpenAPIをRedocly CLIでlintしてbundleし、単一ファイルを出すまでを、PostToolUse hookとredocly.yamlの設定例でまとめます。

大きなOpenAPI定義は、1ファイルのまま育てるとClaude Codeにも人にも扱いづらくなります。$refでpathsやcomponentsを別ファイルに分け、配布用に1ファイルへまとめる運用が定番です。その分割と結合を担うのがRedocly CLIのsplitとbundleで、lintも同じ道具で回せます。

この記事では、分割したOpenAPIをredocly.yamlで管理し、Claude Codeが編集するたびにlintとbundleが走る構成を作ります。bundleで出した単一ファイルをClaude Codeに直接編集させない運用まで含めます。Spectralでlintだけを回したい場合はSpectralのPostToolUse hookで足ります。

split・bundle・joinは何が違うのか

Redocly CLIには、OpenAPIファイルを組み替えるコマンドが3つあります。名前が似ているので、入力と出力で区別します。

コマンド入力出力
split入力1つのOpenAPIファイル出力paths・componentsなどに分かれた複数ファイル
bundle入力ルートのOpenAPIファイル出力$refをたどって集めた単一ファイル(JSONまたはYAML)
join入力複数のOpenAPIファイル出力1つの統合されたAPI定義

bundleはルートファイルから$refをたどり、参照されている部品を自動で取り込みます。部品を明示的に列挙する必要はありません。joinは別々のAPI定義を1つのAPIにまとめるコマンドです。1つのAPIを分割管理しているなら使うのはbundleです。

splitで分けた構成はbundleでそのまま戻せます。開発中は小さなファイルで編集とレビューをし、他のツールに渡すときだけ単一ファイルにする。この使い分けが、Claude Codeと組み合わせる前提になります。

分割した構成とredocly.yamlを用意する

既存の1ファイル定義から始めるなら、splitでディレクトリ構成を作れます。OpenAPI 3.xでは、pathsとwebhooksは1件ずつ、componentsは種類ごとのサブディレクトリに分かれます。

npm i -D @redocly/cli@latest
npx redocly split openapi.yaml --outDir=openapi

生成される構成は次のようになります。

openapi/
  openapi.yaml        # ルート。$refで下を参照する
  paths/              # パスごとに1ファイル
  components/
    schemas/
    parameters/
    responses/

パスのファイル名は、URLの/を区切り文字(既定は_)に置き換えて決まります。--separatorで区切り文字を変えられます。大文字小文字だけが違う名前(Userとuser)は、大文字小文字を区別しないファイルシステムで衝突するため、後から出たほうにuser-2.yamlのような番号が付きます。--file-name-conflicts-severity=errorを付けると、警告で済ませずsplit自体を失敗させられます。

redocly.yamlでルートと出力先を固定する

コマンドにパスを毎回渡すと、Claude Codeが別のパスを渡す余地が生まれます。プロジェクト直下のredocly.yamlに、ルートファイルと出力先を書いておくと引数が要らなくなります。

extends:
  - recommended
 
apis:
  main:
    root: openapi/openapi.yaml
    output: dist/openapi.yaml
 
rules:
  info-license: off
  tag-description: error

apisの下にAPI名を付け、rootにルート、outputにbundleの出力先を書きます。この状態でredocly lintとredocly bundleを引数なしで実行すると、設定済みの全APIが対象になります。逆に、コマンドへAPIを渡した場合は、そのAPIだけが処理されます。--outputが意味を持つのも、コマンドラインで渡したAPIに対してだけです。

bundleで単一ファイルを出す

最小の実行は次の1行です。ルートファイルを渡し、出力先を-oで指定します。

npx redocly bundle openapi/openapi.yaml -o dist/openapi.yaml

出力は既定でYAMLです。JSONにしたいときは--ext jsonを付けるか、出力名を.jsonにします。--outputは既存の出力ファイルを上書きします。

使う場面が分かれるオプション

場面オプション
$refを残さず全部展開したいオプション--dereferenced(-d)
使われていないcomponentsを落としたいオプション--remove-unused-components
エラーがあっても出力だけは欲しいオプション--force(-f)
同名で中身の違うスキーマを失敗にしたいオプション--component-renaming-conflicts-severity=error

--dereferencedは、$refを理解しないツールに渡す場合に使います。JSON出力は循環参照があると使えない、とコマンドのページに注記があります。

同名スキーマの衝突は見落としやすい点です。外部ファイルから取り込んだスキーマに同じ名前で中身の違うものがあると、bundleは既定で警告を出し、重複側をSchema-2のように自動で改名します。警告のまま通ってしまうので、Claude Codeがスキーマを足したときに別名へすり替わっても気づけません。--component-renaming-conflicts-severity=errorにすれば、衝突した時点でbundleが失敗します。

名前の付け方そのものを変える手もあります。--component-names-strategy=titleは、各スキーマのtitleをPascalCaseに変換して名前にします。ファイルのパスや$refの順序に左右されないので、番号付きの名前が後から変わる事態を避けられます。ただしtitleの無いスキーマはエラーになります。

lintのルールをredocly.yamlに書く

redocly lintは、OpenAPIの問題を検査するコマンドです。ルールセットを指定しなければrecommendedが使われ、redocly.yamlにextendsやrulesがあればそちらが優先されます。組み込みのルールセットにはspec・minimal・recommended・recommended-strictがあります。

rulesにはルール名: 重大度の短縮形か、追加オプション付きの形式で書きます。

rules:
  tag-description: error
  boolean-parameter-prefixes:
    severity: error
    prefixes:
      - is
      - can

重大度はerror・warn・offのいずれかです。自作のルールはrule/で始まる名前でsubjectとassertionsを書きます。たとえばoperationIdをキャメルケースに揃えるルールは、次のように書けます。

rules:
  rule/operationId-casing:
    subject:
      type: Operation
      property: operationId
    assertions:
      casing: camelCase

Claude Codeにとっては、規約を文章で渡すよりルールで渡すほうが確実です。守れていなければ、ファイルと行を添えた指摘が返るからです。

指摘の見せ方を変える

出力形式は--formatで選べます。既定はcodeframeで、stylishにすると1件が1行に収まり、hookでClaude Codeに返す量を抑えられます。ほかにjson・github-actions・markdown・junitなどがあります。表示件数は既定で100件までで、--max-problemsで変えられます。

既存の定義に指摘が大量にある状態から始めるなら、--generate-ignore-fileが使えます。実行時点のerrorとwarningを.redocly.lint-ignore.yamlに書き出し、以後は新しく増えた指摘だけを見られます。ただし生成された無視リストは負債の先送りなので、Claude Codeには「無視リストへの追記は禁止」と伝えておく必要があります。

プレビューはpreviewではなくbuild-docsで

「プレビュー」には取り違えやすい点があります。redocly previewは、Redocly製品のプロジェクト(@redocly/framework)用のローカルサーバーを起動するコマンドです。ポートは既定で4000番です。単一のOpenAPIファイルを見たいだけなら、build-docsが向いています。

npx redocly build-docs dist/openapi.yaml -o preview/index.html

build-docsはRedocでAPIドキュメントを描画した、独立したHTMLファイルを作ります。出力先の既定はredoc-static.htmlです。生成したHTMLは、Redocのスクリプトを既定でRedocly CDNから読み込みます。--inlineBundleを付ければHTMLに埋め込めて、CDNに接続できない環境でも表示できます。ページからはRedocのテレメトリが読者のブラウザーから送られるため、不要ならREDOCLY_TELEMETRY=offで生成します。

HTMLはhookのたびに作る必要はありません。見た目の確認が要るときに、Claude Codeへ「bundleしてからbuild-docsで確認用HTMLを作って」と頼めば足ります。

編集のたびにlintとbundleを走らせるhook

ここからが本題です。PostToolUseはツール実行後に動くhookで、Edit|Writeのmatcherを付ければファイル編集の直後だけに絞れます。コマンドには、編集したファイルのパスがJSONで標準入力から渡されます。

hookスクリプト

.claude/hooks/redocly-check.shとして保存し、実行権限を付けます。

#!/bin/bash
FILE=$(jq -r '.tool_input.file_path // empty')
case "$FILE" in
  */dist/*) exit 0 ;;
  */openapi/*.yaml|*/openapi/*.yml) ;;
  *) exit 0 ;;
esac
cd "$CLAUDE_PROJECT_DIR" || exit 0
OUT=$(npx redocly lint 2>&1 \
  && npx redocly bundle 2>&1)
if [ $? -ne 0 ]; then
  echo "$OUT" >&2
  exit 2
fi

分割したファイルのどれを編集しても、lintとbundleの対象はredocly.yamlのルートです。断片だけを単体でlintすると、$refの解決先が足りず意味のある結果になりません。ルートから見て検査するほうが、断片の変更が全体に与える影響まで拾えます。dist/配下はbundleの出力なので、先頭で除外しています。

終了コードを2にしているのは、PostToolUseの時点でツールはすでに実行済みで、止められないからです。それでも終了コード2で終えれば、標準エラー出力がClaude Codeに表示されます。終了コード0で終えたhookの標準エラー出力はClaude Codeには見えないため、失敗を伝えたいときは2で抜けます。

lintのページには終了コードの記載がありません。導入時に、errorを含むファイルでnpx redocly lint; echo $?を実行し、0以外になることを確かめてください。warningだけの場合に0で終わるかどうかも、同じ方法で見られます。

settings.jsonへの登録

プロジェクトの.claude/settings.jsonに、次のように登録します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/redocly-check.sh"
          }
        ]
      }
    ]
  }
}

登録後は/hooksで表示を確認できます。動作確認は、パスのファイルから必須の項目を消す編集をClaude Codeにさせ、次の応答でlintの指摘を踏まえた修正が走るかを見るのが早いです。

CLAUDE.mdで「編集してよい場所」を決める

hookは事後に指摘を返す仕組みです。そもそも間違った場所を編集させないために、CLAUDE.mdへ運用の約束を書いておきます。

## OpenAPI
 
- 編集してよいのは `openapi/` 配下の分割ファイルだけ。`dist/` は直接編集しない
- 変更後は `npx redocly lint` と `npx redocly bundle` が通ること
- `.redocly.lint-ignore.yaml` に指摘を追記して通さない。直せない場合は理由を報告する
- スキーマを追加するときは、既存の名前と衝突しない名前にする

bundleの出力を編集してしまうと、次のbundleで上書きされて変更が消えます。生成物を直させない考え方は、OpenAPI Generatorの生成コードを直させず再生成させる設定と同じです。定義そのものの安全性まで監査したいときは、42Crunchプラグインによる監査と修正が補完になります。

つまずきやすい点

ここまでの構成で起こりやすい失敗は次のとおりです。

  • Nodeが古くて動かない: v2はNode.js v22.12.0以上が必要。hookの実行環境でnode -vを確かめる
  • bundleだけ警告で通る: 同名スキーマの衝突は既定で警告止まり。--component-renaming-conflicts-severity=errorで失敗にする
  • 断片を単体でlintしている: ルートファイルを対象にする。引数なしのredocly lintは設定済みの全APIが対象
  • --forceで通している: エラーがあっても出力されるため、hookのコマンドには付けない
  • distが古い: 編集ツール以外で断片を変えた場合はhookが動かない。手でbundleし直す

lintとbundleを別々の命令にせず、1つのhookにまとめるのが、この構成の要点です。指摘が出たら直し、通ったらdistが最新になる。その往復をClaude Codeが自分で回せるようになります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. redocly.com/docs/cli
  2. redocly.com/docs/cli/commands/bundle
  3. redocly.com/docs/cli/commands/split
  4. redocly.com/docs/cli/commands/lint
  5. redocly.com/docs/cli/commands/build-docs
  6. code.claude.com/docs/en/hooks-guide
  7. code.claude.com/docs/en/hooks
この記事を共有:XはてブLinkedIn