Skip to content

feat: 同期時に新規/更新を区別して表示し更新時のみ確認プロンプトを出す#22

Merged
polidog merged 3 commits into
mainfrom
feat/18
Jun 27, 2026
Merged

feat: 同期時に新規/更新を区別して表示し更新時のみ確認プロンプトを出す#22
polidog merged 3 commits into
mainfrom
feat/18

Conversation

@polidog

@polidog polidog commented Jun 26, 2026

Copy link
Copy Markdown
Member

タスク

送信前に provider へ API で問い合わせ、登録対象 key が provider 側に既に存在するか確認し、各 Entry を「新規」/「更新(上書き)」に分類する。

  • Vercel: GET /v10/projects/{projectId}/env で既存 key 一覧を取得し key の一致で更新判定
  • GitHub: 変数は既存 githubVariableExists を活用、シークレットは名前確認 API (githubSecretExists) を新設
  • 確認画面を更新/新規が分かる形に変更(⟳ KEY [更新]+ KEY [新規]
  • 更新対象がある場合のみ確認プロンプトを表示(新規のみなら確認なしで送信)
  • --yes/-y 既存フラグで確認をスキップ(新しい --force は追加しない)
  • --dry-run 時もトークンがあれば新規/更新を分類して表示
  • 非対話環境で更新対象かつ --yes 未指定の場合はエラー停止し --yes を案内

完了条件

  • 送信前に provider への問い合わせで登録対象 key の既存/非存在を判定し、各 Entry を「新規」「更新」に分類(Vercel・GitHub 両対応)
  • 確認画面に新規件数・更新件数と、各 key が新規か更新かが表示される
  • 更新(既存値の上書き)対象がある場合に確認プロンプトが出る
  • --yes または -y を付けると確認をスキップして送信する
  • 非対話環境で -y 未指定かつ確認が必要な場合は従来同様エラー停止し --yes を案内
  • --dry-run の一覧出力にも新規/更新の区別が反映される
  • 分類ロジックのユニットテストが追加され、go test ./... が通る
  • README と printUsage の記述が新挙動に更新されている

動作確認

  • lint / build 通過(go vet ./... / go build ./...
  • 分類ロジックのユニットテスト追加・通過(classifyVercelItems / classifyGitHubTasksByExistence
  • go test -race ./... 通過

主な変更点

internal/provider/vercel/vercel.go

  • vercelFetchExistingKeys(): GET /v10/projects/{id}/env で既存 key セットを取得
  • classifyVercelItems(): items を既存 key セットと照合して新規/更新に分類する純粋関数
  • countClassified(): 新規/更新件数を集計するヘルパー
  • Sync(): dry-run 時も分類を試みる。更新がある場合のみ確認プロンプト表示

internal/provider/github/github.go

  • githubSecretExists(): シークレットの名前確認 API
  • classifyGitHubTasksByExistence(): exists 関数を注入して分類する純粋関数(テスト容易性)
  • classifyGitHubTasks(): 実 API を使って分類するラッパー
  • countGitHubClassified(): 件数集計ヘルパー
  • Sync(): 同上

cmd/env-sync/main.go

  • dry-run の早期リターンを削除(各 provider が dry-run を処理し分類表示する)
  • printUsage(): 新挙動の説明を追加

テスト追加

  • internal/provider/vercel/classify_test.go
  • internal/provider/github/classify_test.go

- Vercel: GET /v10/projects/{id}/env で既存 key を取得し新規/更新を分類
- GitHub: secret/variable ごとに API で存在確認して新規/更新を分類
- 確認プロンプトは更新(上書き)対象がある場合のみ表示する(新規のみなら即送信)
- dry-run でもトークンがあれば新規/更新を分類して表示する
- 非対話環境で更新対象かつ --yes 未指定の場合はエラー停止し --yes を案内
- 分類ロジックは純粋関数(classifyVercelItems / classifyGitHubTasksByExistence)に切り出しユニットテストを追加
- README と printUsage を新挙動に更新

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdeTedorn5WqsyAJt4bybB

Copilot AI 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.

Pull request overview

送信前に各 provider へ問い合わせてキーの「新規/更新」を分類し、更新がある場合のみ確認プロンプトを出すことで、同期時の安全性と視認性を上げる PR です。

Changes:

  • Vercel/GitHub ともに既存キーを照会して新規/更新を分類し、一覧表示に反映
  • 更新がある場合のみ確認プロンプトを表示し、--yes/-y でスキップ可能に変更
  • --dry-run でも(トークンがあれば)分類して表示する挙動に更新し、README/usage を追従

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
README.md 新規/更新表示・確認条件・dry-run の挙動をドキュメント化
internal/provider/vercel/vercel.go 既存 key 取得・分類・更新時のみ確認のロジックを追加
internal/provider/vercel/classify_test.go Vercel 側の分類/件数カウント/既存取得に関するテストを追加
internal/provider/github/github.go Secret の存在確認 API 追加、分類/件数カウント、更新時のみ確認に対応
internal/provider/github/classify_test.go GitHub 側の分類/件数カウントのテストを追加
cmd/env-sync/main.go dry-run の早期 return を廃止し provider 側で dry-run 表示を行うよう変更、usage 更新

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +127 to +142
origBase := apiBase
// テスト用に apiBase を差し替える(package レベル変数を経由しないため直接書き換え)
// vercel.go の apiBase は const なので httptest URL を直接 projectID に組み込む
_ = origBase // suppress unused warning

client := &http.Client{}
// テスト用 URL を使うため vercelFetchExistingKeys を直接呼ぶには apiBase を差し替える必要がある。
// ここでは URL 構築ロジックを検証する別の方法として httptest.Server の URL を projectID に含めた
// カスタム URL で呼ぶ(テスト用ヘルパー経由)。
_ = client

// apiBase が const のため URL 組み立てのテストは httptest URL を直接呼ぶ方法で行う
u := srv.URL + "/v10/projects/test-project/env"
req, _ := http.NewRequest(http.MethodGet, u, nil)
req.Header.Set("Authorization", "Bearer test-token")
res, err := http.DefaultClient.Do(req)
Comment on lines +69 to +75
if token != "" {
existing, err := vercelFetchExistingKeys(client, token, projectID, teamID)
if err == nil {
classified = classifyVercelItems(items, existing)
}
// API 失敗時は classified = nil のまま(確認スキップしない安全側フォールバック)
}
Comment on lines +72 to +75
found, err := exists(t)
if err != nil {
return nil, fmt.Errorf("%s: 存在確認失敗: %w", t.entry.Key, err)
}
Comment on lines +127 to +133
if token != "" {
cls, err := classifyGitHubTasks(client, token, owner, repo, tasks)
if err == nil {
classified = cls
}
// API 失敗時は classified = nil のまま(安全側フォールバック)
}
- vercel: apiBase を var 化し、TestVercelFetchExistingKeys_Success が実際に
  vercelFetchExistingKeys を呼んで URL/ヘッダ/ステータス/パースを検証する形に修正
- vercel: 既存 key 取得失敗時に分類スキップ理由を stderr へ警告表示
- github: 存在確認失敗時のエラー文に envScope を含めて切り分け可能に
- github: 分類 API 失敗時に分類スキップ理由を stderr へ警告表示

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdeTedorn5WqsyAJt4bybB

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Comment on lines +219 to +221
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("既存 key 取得失敗: HTTP %d", res.StatusCode)
}
Comment thread internal/provider/github/github.go Outdated
return tasks
}

// classifyGitHubTasksByExistence は exists 関数を使って各タスクを新規/更新に分類する純粋関数。
Comment thread internal/provider/github/github.go Outdated
Comment on lines +234 to +238
// variable: GET で存在確認 → POST or PATCH
exists, e := githubVariableExists(client, token, owner, repo, t.envScope, t.entry.Key)
if e != nil {
fmt.Printf("✗ %s -> 存在確認失敗: %s\n", t.entry.Key, e)
scope := t.envScope
if scope == "" {
Comment on lines +68 to +70
// ---- 既存 key を問い合わせて新規/更新を分類 ----
client := &http.Client{}
var classified []classifiedVercelItem
Comment on lines +128 to +130
// ---- 既存確認して新規/更新を分類 ----
client := &http.Client{}
var classified []githubClassifiedTask
Comment on lines +449 to +479
// githubSecretExists は GitHub Actions のシークレットが存在するかを確認する。
func githubSecretExists(client *http.Client, token, owner, repo, envScope, name string) (bool, error) {
var apiURL string
if envScope == "" {
apiURL = fmt.Sprintf("%s/repos/%s/%s/actions/secrets/%s",
githubAPIBase, url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(name))
} else {
apiURL = fmt.Sprintf("%s/repos/%s/%s/environments/%s/secrets/%s",
githubAPIBase, url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(envScope), url.PathEscape(name))
}

req, err := http.NewRequest(http.MethodGet, apiURL, nil)
if err != nil {
return false, fmt.Errorf("リクエスト生成失敗: %w", err)
}
setGitHubHeaders(req, token)

res, err := client.Do(req)
if err != nil {
return false, fmt.Errorf("リクエスト失敗: %w", err)
}
defer res.Body.Close()

if res.StatusCode == http.StatusOK {
io.Copy(io.Discard, res.Body) //nolint:errcheck // drain で接続を再利用可能にする
return true, nil
}
if res.StatusCode == http.StatusNotFound {
io.Copy(io.Discard, res.Body) //nolint:errcheck // drain で接続を再利用可能にする
return false, nil
}
- vercel: vercelFetchExistingKeys の非200エラーにレスポンスボディの詳細を含める
- vercel/github: http.Client に 30s タイムアウトを設定し無期限ハングを防止
- github: variable 送信時に classified を再利用し、存在確認 API の二重呼び出しを解消(classified==nil のときのみフォールバック)
- github: classifyGitHubTasksByExistence のコメントを「純粋関数」から「存在確認関数を注入」に訂正
- github: githubSecretExists の httptest テスト(repo/env スコープ・200/404/error)を追加

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdeTedorn5WqsyAJt4bybB

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@polidog polidog merged commit a820d0e into main Jun 27, 2026
2 checks passed
@polidog polidog deleted the feat/18 branch June 27, 2026 03:18
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