Skip to content

refactor: package 構成を cmd/+internal/ へ再編#17

Merged
polidog merged 3 commits into
mainfrom
feat/15
Jun 26, 2026
Merged

refactor: package 構成を cmd/+internal/ へ再編#17
polidog merged 3 commits into
mainfrom
feat/15

Conversation

@polidog

@polidog polidog commented Jun 26, 2026

Copy link
Copy Markdown
Member

タスク

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/ Options/Definition/VarConf/ParseFlags、.env パーサ、ヘルパー、init
internal/sync/ 共通ドメイン解決 ResolveEntries
internal/provider/ Provider interface・registry・共通ドメイン型 Entry/Options
internal/provider/vercel/ vercelProvider(init() で自己登録)
internal/provider/github/ githubProvider(init() で自己登録)
  • 循環参照を避けるため、共通ドメイン型 Entry/Optionsinternal/provider に集約。依存方向は provider 実装 → provider/configsync → provider/configconfig → providermain → 全体 の一方向。
  • provider の自己登録(init() + RegisterProvider)を維持し、cmd/env-sync/main.go の blank import 経由で registry に登録。--provider vercel / --provider github は従来どおり動作。
  • *_test.go を対応実装と同パッケージへ移動(package main 依存を解消)。
  • --versionParseFlagsversionFn を注入する形にし、引数の出現順に処理する旧挙動を維持。
  • RegistryForTestexport_test.go 化し本番ビルドの API 面から除外。
  • .goreleaser.yamlmain: ./cmd/env-sync、ldflags フルパス化)、README、docs/architecture.md、docs/test-architecture.md を新構成に更新。

完了条件

  • package main が cmd/env-sync/ のみに存在し、ルート直下に *.go が残っていない
  • ロジックが internal/config・internal/sync・internal/provider・vercel・github へ責務ごとに分割
  • provider 自己登録が維持され --provider vercel/github が従来どおり動作
  • 各テストが対応実装と同パッケージに配置され package main へ依存しない
  • パッケージ間に循環参照がない
  • .goreleaser.yaml の build main が ./cmd/env-sync を指す
  • README・docs が新構成に更新
  • 既存 CLI 挙動(init 含む)・出力・エラーハンドリングに退行がない

動作確認

  • gofmt -l . 差分なし / go vet エラーなし / go build 成功 / go test 全6パッケージ pass
  • 設計・計画適合レビューで Warning 2件検出 → 修正済み(--version 処理位置、RegistryForTest 公開)
  • 統合テスト3件追加(init 結線・github provider 解決・provider 振り分け)

🤖 Generated with Claude Code

フラットな単一 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>

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

フラットな単一 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 ResolveEntriesinternal/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 thread internal/config/helpers_test.go Outdated
Comment on lines +9 to +10
// go.mod は必ず存在するファイルとして使用する。
// テストはモジュールルートから実行されるため相対パスが通る。

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 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 名を決定的に並べ替えてから実行してください。

Comment thread internal/config/config_test.go Outdated
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>

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 27 out of 28 changed files in this pull request and generated 2 comments.

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(ファイルは存在する)")
}
}
Comment thread docs/test-architecture.md Outdated
## ファイル配置

- 実装と**同階層・同パッケージ**(`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>
@polidog polidog requested a review from Copilot June 26, 2026 16:22
@polidog polidog removed the request for review from Copilot June 26, 2026 16:24
@polidog polidog merged commit d473089 into main Jun 26, 2026
2 checks passed
@polidog polidog deleted the feat/15 branch June 26, 2026 16:26
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