Skip to content

feat(cli): CLI のリリースバイナリ配布と --json 出力を追加 - #7

Merged
sousuke0422 merged 10 commits into
mainfrom
feat/cli-release-binaries
Jul 31, 2026
Merged

feat(cli): CLI のリリースバイナリ配布と --json 出力を追加#7
sousuke0422 merged 10 commits into
mainfrom
feat/cli-release-binaries

Conversation

@yupix

@yupix yupix commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

概要

vrt CLI をリリースバイナリとして配布できるようにし、あわせて機械可読な --json 出力を追加する。

動機

CLI がリリース配布されていないため、利用側のリポジトリが CI で毎回ソースからビルドする必要があり、初回で 15〜20 分かかっていた(実測: koyori-app/task の PR #491)。さらに利用側のワークフローに「Cargo ワークスペースが apps/backend にある」「mold リンカが必要」といった VRT 内部の事情が漏れていた。CLI は公開インターフェースなので、成果物として配るのが筋。

--json は GitHub Action などプログラムから呼ぶ側のために必要になる。現状 CLI は人間向けのログしか出さないため、呼び出し元が build ID や結果を取得するにはログを正規表現で削るしかない。

変更内容

1. リリースワークフロー(.github/workflows/release-cli.yml

  • トリガー: cli-v* タグの push(バックエンド本体のリリースと分離するための prefix)。workflow_dispatch はビルドの素振り用で、Release は作らない(後述)

  • ターゲット: 4 種をすべてネイティブランナーでビルド(クロスコンパイルは mold との相性が悪いため避けた)

    target runner
    x86_64-unknown-linux-gnu ubuntu-latest
    aarch64-unknown-linux-gnu ubuntu-24.04-arm
    x86_64-apple-darwin macos-15-intel
    aarch64-apple-darwin macos-15

    macOS のラベルは明示的に固定している。macos-latest は 1〜2 か月かけて新しい OS へ段階移行するため、リリース成果物の再現性が保てない。-large / -xlarge は larger runner で public リポジトリでも課金対象になるので使わない(標準ランナーは public リポジトリでは無料)。

  • 成果物: vrt-<target>.tar.gz(バイナリ 1 本のみ、ディレクトリで包まない)と vrt-<target>.tar.gz.sha256。ダウンロード側が URL を組み立てられるよう命名を固定している

  • permissions はトップで contents: read、Release 作成ジョブだけ contents: write

  • mold は Linux ランナーのみ導入(apps/backend/.cargo/config.toml が Linux ターゲットで要求するため)

2. vrt upload --json

指定すると stdout に 1 行の JSON を出力する。

{"build_id":"...","build_number":123,"tenant_slug":"koyori","project_slug":"task","status":"changes_detected","exit_code":1}
  • status--wait 指定時は最終状態、非指定時は finalize 直後の状態
  • exit_code は CLI の終了コードと同じ(passed/approved=0、changes_detected=1、その他=2)
  • tracing のログ出力先を stdout から stderr に変更した。 --json の有無に関わらず常に stderr へ出す。CLI のログは stderr に出すのが一般的で、これにより stdout を結果専用に空けられる
  • 既存の終了コードの意味と --wait の挙動は変えていない
  • ビルド作成前に失敗した場合は JSON を出さず、stdout が空のまま終了コード 2 で終わる。 呼び出し元は「stdout が空で非ゼロ終了」を処理する必要がある(README に明記)

3. README

リリースバイナリの入手手順を推奨として先に置き、ソースビルドの記述も残した。--json の説明と失敗時の挙動も追記。

レビュー対応(コミット dc216fe

いただいた指摘 5 件すべてに対応した。

  1. 【要修正】workflow_dispatch で release ジョブが失敗する — release ジョブに if: startsWith(github.ref, 'refs/tags/cli-v') を追加し、on: 側にも手動実行の位置づけ(ビルドの素振り、Release は作らない)をコメントで書いた
  2. --json 時のエラー経路 — README に「ビルド作成前の失敗では JSON を出さない」旨を明記
  3. doc コメントの取り残しpoll_until_terminal の doc を --json 時は stderr になる旨を含めて修正
  4. README のチェックサム検証例.sha256 はアーカイブ名込みで記録されるため、配布時のファイル名のまま取得しないと shasum -c が通らない。例を修正し検証の一行を追加
  5. PR 本文の表が古い — この本文を更新済み(macos-13macos-15-intel

あわせて exit_code_for / is_settled のユニットテストを 3 件追加した(推奨いただいた分)。終了コードは CI の合否判定そのものなので 8 状態すべてを固定し、未知の状態が黙って 0(成功)に倒れないことも明示的に押さえている。

経緯(レビュー前に自分で直した分)

  • BuildResponse.number#[serde(default)] を外した。OpenAPI の required に含まれる必須フィールドで、default があると契約変更時に黙って 0 になり、呼び出し元が /builds/0 という存在しないレビュー URL を作ってしまうため
  • macos-13actions/runner-images で提供が終了していた(現在の提供は macOS 15 / 26 系、14 は deprecated)。そのままではリリースジョブが「該当ランナー無し」で起動できなかった

確認したこと

  • cargo test -p vrt-cli 21 件 PASS(既存 18 + 新規 3)、cargo clippy -p vrt-cli --all-targets -- -D warnings / cargo fmt --all -- --check 通過
  • vrt upload --help--json が出ることを確認
  • ワークフローの YAML パース OK、uses: すべて 40 桁 SHA でピン留め
  • 新規に追加した 3 つの action の SHA が実在し、かつ主張しているバージョンタグの commit と一致することを GitHub API で照合済み(actions/download-artifact v7.0.0 / softprops/action-gh-release v2.4.1 / actions/upload-artifact v7.0.1)
  • ランナーラベルの実在を actions/runner-images の README で確認(ubuntu-24.04-arm / macos-15 / macos-15-intel

未検証: リリースワークフロー自体は実行していない。タグを切って初めて動く。

後続

マージ後に cli-v0.1.0 タグを切ると Release が作られる。それを koyori-app/vrt-actions#1 がダウンロードして使う。

yupix added 2 commits July 30, 2026 21:45
利用側リポジトリが CI で毎回 vrt CLI をソースからビルドしていた(初回 15〜20 分、
mold や apps/backend ワークスペースといった内部事情も漏れていた)ため、リリース
バイナリ配布と機械可読出力を追加する。

- .github/workflows/release-cli.yml を新規追加。`cli-v*` タグ(と手動実行)で
  4 ターゲット(x86_64/aarch64 の linux-gnu と apple-darwin)をネイティブランナーで
  ビルドし、`vrt-<target>.tar.gz`(バイナリ vrt 1 本)と `.sha256` を GitHub Release
  へ添付する。クロスコンパイルは mold と相性が悪いので避けた。action はすべて
  40 桁 SHA でピン留めし、既存ワークフローの SHA を再利用した。

- vrt upload に `--json` を追加。build_id / build_number / tenant_slug /
  project_slug / status / exit_code を stdout へ 1 行 JSON で出す。GitHub Action
  などの呼び出し元が結果をパースできるようにする。build_number 用に CLI 側の
  BuildResponse へ number フィールドを追加した。

- tracing のログ出力先を stderr に変更(`--json` の有無に関わらず)。stdout を
  結果専用に空け、機械可読出力とログが混ざらないようにする。進捗ログと人間向け
  サマリも `--json` 時は stdout に出さない。終了コードと --wait の挙動は不変。

- README にリリースバイナリの入手手順(推奨)と `--json` の説明を追記。
`number` は BuildResponse の必須フィールド(OpenAPI の required に含まれる)
なので `#[serde(default)]` を外す。

default が付いていると、サーバー側の契約が変わって欠落した場合に黙って 0 に
なり、`--json` の build_number が 0 として出る。呼び出し元がこれで
`/builds/0` という存在しないレビュー URL を組み立ててしまうため、
契約違反は deserialize の失敗として顕在化させる。

cargo test -p vrt-cli は 18 件とも PASS。
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

yupix added 8 commits July 30, 2026 21:59
actions/runner-images で現在提供されている macOS イメージは 15 / 26 系のみで、
14 は deprecated、13 は既に一覧から消えている。macos-13 のままではリリース
ジョブが「該当ランナー無し」で起動できない。

Intel 版は `-intel` サフィックスの macos-15-intel、Apple Silicon は macos-15 を
使う。あわせて macos-latest の使用をやめてラベルを固定する。-latest は 1〜2 か月
かけて新しい OS へ段階移行するため、リリース成果物の再現性が保てないため。

larger runner(`-large` / `-xlarge`)は public リポジトリでも課金対象になるので
選ばない。標準ランナーは public リポジトリでは無料。
- release ジョブに `if: startsWith(github.ref, 'refs/tags/cli-v')` を追加。
  action-gh-release は tag_name 未指定だと GITHUB_REF をタグとして扱うため、
  workflow_dispatch をブランチから手動実行すると必ず失敗していた。
  手動実行はビルドの素振り用と位置づけ、その旨を on: にもコメントで書く。

- README のダウンロード例を、チェックサム検証が実際に通る形に直す。
  .sha256 はアーカイブ名込みで記録されるため、配布時のファイル名のまま
  取得しないと `shasum -a 256 -c` が通らない。検証の一行も例に含める。

- README に `--json` の失敗時の挙動を明記。ビルド作成前に失敗した場合は
  stdout が空のまま終了コード 2 で終わるため、呼び出し元は「stdout が空で
  非ゼロ終了」を処理する必要がある。

- poll_until_terminal の doc コメントが「進捗ログを stdout に流す」のまま
  だったのを、`--json` 時は stderr になる旨を含めて修正。

- exit_code_for と is_settled のユニットテストを追加(3 件)。終了コードは
  CI の合否判定そのものなので 8 状態すべてを固定し、未知の状態が黙って
  0(成功)に倒れないことも明示的に押さえる。

cargo test -p vrt-cli は 21 件 PASS、clippy / fmt もクリーン。
finalize 成功後の poll_until_terminal が一時的な通信失敗や 30 分タイムアウトで
Err を返すと、run_upload の `?` が早期リターンして stdout に JSON が 1 行も
出なかった。README は「finalize まで到達すれば JSON が出る」契約を謳うため、
呼び出し元が build_id すら取れなくなっていた。

--wait && --json のときは poll の Err を明示的に処理し、finalize 済みの既知情報
(build_id/build_number/slug/finalize 直後の status) に exit_code=2 と失敗理由の
error フィールドを添えて JSON を 1 行出す。エラーは従来どおり stderr に tracing で
残し、終了コード 2 で終わる。--json でない場合は従来どおり Err を伝播する。

JSON 値の組み立てを json_result_value 純関数に切り出してユニットテスト可能にした。
PR #7 のレビュー指摘 3 件に対応する。

- --json のビルド終端パスが error_message を落としていた。run_upload の
  poll 成功後の print_json_result に final_build.error_message.as_deref() を
  渡し、failed / rejected で終わったビルドの失敗理由を人間向け report() と
  同様に JSON の error にも載せる。成功時は error キーが出ない契約は維持。
  ユニットテストを 1 件追加し、README の --json 説明にこの経路を追記した。
- release-cli.yml の dtolnay/rust-toolchain に toolchain: 1.95.0 を明示。
  action は rust-toolchain.toml を読まないため、無指定だと stable と 1.95.0
  の 2 本をダウンロードしていた。コメントも実態に合わせて修正した。
- release-cli.yml から Swatinem/rust-cache を削除。GitHub Actions キャッシュは
  default ブランチから子 ref に読めるため、細工されたエントリがリリース
  バイナリに混入しうる。再現性も損なうため、リリースはクリーンビルドにする。
cargo build の成功だけを根拠に tar → Release へ進むと、リンクは通ったが
起動時に落ちる類の破損(動的リンク先の欠落など)が捕まらないまま配布される。
4 ターゲットともネイティブランナーでビルドしているため、パッケージ直前に
vrt --version を実行して起動確認する。
@sousuke0422
sousuke0422 merged commit 4945b37 into main Jul 31, 2026
5 checks passed
@sousuke0422
sousuke0422 deleted the feat/cli-release-binaries branch July 31, 2026 03:21
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.

2 participants