Conversation
フラットな単一 package main(ルート直下の *.go)を golang-standards/project-layout 風の cmd/+internal/ 構成へ分割。 - cmd/env-sync/ オーケストレーション・printUsage・die・main - internal/config/ options/definition/parseFlags/.env パーサ/ヘルパー - internal/sync/ 共通ドメイン解決 ResolveEntries - internal/provider/ Provider interface・registry・Entry・Options - internal/provider/vercel/ vercelProvider(init で自己登録) - internal/provider/github/ githubProvider(init で自己登録) provider の自己登録は main の blank import で維持。各テストを対応実装と 同パッケージへ移動。循環参照回避のため Entry/Options は internal/provider に集約。--version は versionFn 注入で旧挙動を維持。RegistryForTest は export_test.go 化。.goreleaser.yaml・README・docs を新構成に更新。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
フラットな単一 package main 構成を cmd/env-sync + internal/{config,sync,provider,...} の project-layout 風構成へ再編し、責務分離と循環参照回避を維持しつつ既存 CLI 挙動(provider 自己登録、--version の順序処理、init サブコマンド等)を保つ PR です。
Changes:
cmd/env-sync以外のロジックをinternal/config/internal/sync/internal/providerおよび各 provider 実装パッケージへ移動し、共通ドメイン型をinternal/providerに集約ParseFlags/ init サブコマンド周りをinternal/configに移し、--versionを注入可能なversionFnで維持- テスト配置の再編・統合テスト追加、README / docs / GoReleaser の参照パス更新
Reviewed changes
Copilot reviewed 27 out of 28 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | go install / build ターゲットを ./cmd/env-sync に更新 |
| provider.go | 旧 package main の provider registry を削除(internal/provider へ移管) |
| internal/sync/entry.go | ResolveEntries を internal/sync として切り出し、provider.Entry を返すよう更新 |
| internal/sync/entry_test.go | internal/sync 配下へ移動し、新 package 構成に追随 |
| internal/provider/provider.go | 共通の Entry/Options/Provider と registry API を新設 |
| internal/provider/provider_test.go | registry のブラックボックステスト化(provider 実装を blank import) |
| internal/provider/export_test.go | テスト専用の registry アクセサを追加(本番 API から除外) |
| internal/provider/vercel/vercel.go | Vercel provider を独立パッケージ化し、registry 自己登録を維持 |
| internal/provider/vercel/vercel_test.go | Vercel provider のテストを同パッケージへ移動 |
| internal/provider/github/github.go | GitHub provider を独立パッケージ化し、registry 自己登録を維持 |
| internal/provider/github/github_test.go | GitHub provider のテストを同パッケージへ移動 |
| internal/provider/github/github_integration_test.go | GitHub provider の統合テストを同パッケージへ移動 |
| internal/config/config.go | YAML モデル/フラグパースを internal/config に移管し provider.Options を返す |
| internal/config/config_test.go | ParseFlags / YAML パースのテストを追加(※ --version テストに要修正点あり) |
| internal/config/dotenv.go | dotenv パーサを ParseDotenv として internal/config に移動 |
| internal/config/dotenv_test.go | dotenv テストを移動し新 API 名へ追随 |
| internal/config/helpers.go | FileExists/IsTTY/SortedKeys 群を internal/config へ移動 |
| internal/config/helpers_test.go | helpers テストを internal/config へ移動(※重複/不整合コメントあり) |
| internal/config/init.go | init サブコマンド処理を internal/config に移管し、usage 注入に対応 |
| internal/config/init_test.go | init サブコマンドのテストを internal/config に追随 |
| helpers.go | 旧 package main の helper 群を削除(internal/config へ移管) |
| helpers_test.go | 旧 helper テストを削除(internal/config へ移管) |
| config_test.go | 旧 package main の config テストを削除(internal/config へ移管) |
| docs/architecture.md | 新ディレクトリ構成・依存方向・用語を更新 |
| docs/test-architecture.md | テスト配置の説明を新構成に更新 |
| cmd/env-sync/main.go | main のオーケストレーションに限定し、blank import で provider 自己登録を維持 |
| cmd/env-sync/main_test.go | バイナリ統合テスト(--version / --help / --dry-run)を追加 |
| .goreleaser.yaml | build main を ./cmd/env-sync にし、ldflags をフル import path 指定へ更新 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+98
to
+122
| func TestParseFlags_VersionFn_Called(t *testing.T) { | ||
| for _, arg := range []string{"--version", "-version"} { | ||
| arg := arg | ||
| t.Run(arg, func(t *testing.T) { | ||
| called := false | ||
| // os.Exit を直接呼ぶため goroutine + recover でテストする。 | ||
| // versionFn が呼ばれたことを記録し、os.Exit(0) の代わりに panic でフロー中断。 | ||
| done := make(chan bool, 1) | ||
| go func() { | ||
| defer func() { | ||
| if r := recover(); r != nil { | ||
| done <- called | ||
| } else { | ||
| done <- called | ||
| } | ||
| }() | ||
| config.ParseFlags([]string{arg}, nil, func() { called = true }) | ||
| done <- called | ||
| }() | ||
| if got := <-done; !got { | ||
| t.Errorf("%s: versionFn が呼ばれなかった", arg) | ||
| } | ||
| }) | ||
| } | ||
| } |
Comment on lines
+10
to
+14
| // FileExists はパスが存在するかを返す。 | ||
| func FileExists(path string) bool { | ||
| _, err := os.Stat(path) | ||
| return err == nil | ||
| } |
Comment on lines
+63
to
+72
| func TestFileExists_PermError(t *testing.T) { | ||
| f, err := os.CreateTemp(t.TempDir(), "test-*.txt") | ||
| if err != nil { | ||
| t.Skip("一時ファイル作成失敗") | ||
| } | ||
| f.Close() | ||
| if !FileExists(f.Name()) { | ||
| t.Errorf("FileExists(一時ファイル) = false, want true") | ||
| } | ||
| } |
Comment on lines
+9
to
+10
| // go.mod は必ず存在するファイルとして使用する。 | ||
| // テストはモジュールルートから実行されるため相対パスが通る。 |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
internal/config/config.go:75
- --env= の値が空でも受理され、後段で「見つかりません: 」のような分かりにくいエラーになります。ParseFlags の段階で空値をエラーにしてください。
internal/config/config.go:79 - --def= の値が空でも受理され、後段で「見つかりません: 」のような分かりにくいエラーになります。ParseFlags の段階で空値をエラーにしてください。
cmd/env-sync/main.go:89 - FileExists で事前チェックすると権限エラー等でも「見つかりません」と誤表示になります(internal/config/init.go ではその理由で事前チェックを避けています)。ReadFile のエラーで os.IsNotExist を分岐し、その他は「読み込みに失敗」としてパスも含めたメッセージにしてください。
cmd/env-sync/main.go:169 - provider の「登録順」は各パッケージの init() 実行順に依存しますが、パッケージ間の init 順序は Go 仕様上保証されません。そのため複数 provider を指定したときの同期順序がビルド環境等で変わり得ます。CLI の挙動を安定させるため、このループでは provider 名を決定的に並べ替えてから実行してください。
| done <- called | ||
| } | ||
| }() | ||
| config.ParseFlags([]string{arg}, nil, func() { called = true }) |
- FileExists: err==nil を !os.IsNotExist(err) に変更し権限エラーを誤判定しない - TestParseFlags_VersionFn_Called: versionFn 内で panic し os.Exit(0) に到達させない - TestFileExists_PermError: 実際に権限エラーを再現するテストに修正 - TestFileExists_Exists: 古いコメント(go.mod 使用)を削除 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Comment on lines
+61
to
+79
| func TestFileExists_PermError(t *testing.T) { | ||
| if os.Getuid() == 0 { | ||
| t.Skip("root では権限エラーを再現できない") | ||
| } | ||
| dir := t.TempDir() | ||
| f, err := os.CreateTemp(dir, "perm-*.txt") | ||
| if err != nil { | ||
| t.Skip("一時ファイル作成失敗") | ||
| } | ||
| f.Close() | ||
| // ファイルのパーミッションを 000 にして読み取り不能にする | ||
| if err := os.Chmod(f.Name(), 0o000); err != nil { | ||
| t.Skip("chmod 失敗") | ||
| } | ||
| // ファイルは存在するが stat でアクセスできない → FileExists は true を返すべき | ||
| if !FileExists(f.Name()) { | ||
| t.Error("FileExists(権限なしファイル) = false, want true(ファイルは存在する)") | ||
| } | ||
| } |
| ## ファイル配置 | ||
|
|
||
| - 実装と**同階層・同パッケージ**(`package main`)に `<対象>_test.go` を置く(`config_test.go` / `entry_test.go` / `provider_test.go` / `vercel_test.go` / `github_test.go` / `dotenv_test.go` / `helpers_test.go` / `init_test.go`)。 | ||
| - 実装と**同階層・同パッケージ**に `<対象>_test.go` を置く。各テストは対応する実装パッケージに同居する: |
- TestFileExists_PermError: ファイルではなく親ディレクトリのexec権限を 落として os.Stat が EACCES になる状況を再現。t.Cleanup で権限を復元 - docs/test-architecture.md: 「同パッケージ」を「同ディレクトリ、必要に 応じて *_test 外部テストパッケージを使う」に修正 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
タスク
docs/architecture.md「今後の方向(任意)」に沿って、フラットな単一
package main(ルート直下の *.go)を golang-standards/project-layout 風のcmd/+internal/構成へ再編する(タスク #15)。変更内容
cmd/env-sync/package mainはここのみ。run() のオーケストレーション・printUsage・die・main・init の結線internal/config/internal/sync/ResolveEntriesinternal/provider/Providerinterface・registry・共通ドメイン型Entry/Optionsinternal/provider/vercel/vercelProvider(init() で自己登録)internal/provider/github/githubProvider(init() で自己登録)Entry/Optionsをinternal/providerに集約。依存方向はprovider 実装 → provider/config、sync → provider/config、config → provider、main → 全体の一方向。cmd/env-sync/main.goの blank import 経由で registry に登録。--provider vercel/--provider githubは従来どおり動作。*_test.goを対応実装と同パッケージへ移動(package main 依存を解消)。--versionはParseFlagsにversionFnを注入する形にし、引数の出現順に処理する旧挙動を維持。RegistryForTestはexport_test.go化し本番ビルドの API 面から除外。.goreleaser.yaml(main: ./cmd/env-sync、ldflags フルパス化)、README、docs/architecture.md、docs/test-architecture.md を新構成に更新。完了条件
package mainが cmd/env-sync/ のみに存在し、ルート直下に *.go が残っていない--provider vercel/githubが従来どおり動作動作確認
🤖 Generated with Claude Code