Class: Space::Src::CLI::Sync::Run

Inherits:
Dry::CLI::Command
  • Object
show all
Includes:
GlobalOptions
Defined in:
lib/space_src/cli/sync.rb

Instance Method Summary collapse

Methods included from GlobalOptions

included

Instance Method Details

#call(repo: nil, plain: nil, json: nil, no_color: nil, quiet: nil) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/space_src/cli/sync.rb', line 29

def call(repo: nil, plain: nil, json: nil, no_color: nil, quiet: nil, **)
  paths = CLI.make_paths
  paths.ensure!
  config = Config::Store.load(paths.config_file).success

  # Log rotation pre-step (slice-4 gate G5). launchd owns
  # the stdout/stderr redirect via StandardOutPath /
  # StandardErrorPath; the sync process rotates those
  # files at the start of each run so the previous run's
  # log doesn't grow unbounded. The rotator renames the
  # file to a timestamped archive (preserving bytes); the
  # current process's inherited fd still points to the
  # renamed file, so writes during this run succeed.
  # launchd opens a fresh file at the original path on
  # the next spawn. No-op when the log is missing or
  # under-threshold (sync tests in G4 stay green).
  rotate_plist_logs(paths)

  # Resolved BEFORE the scoping block: the "scoping sync to:"
  # notice below is prose, and must be suppressed in JSON mode
  # for the same reason the summary line is. Resolution depends
  # only on flags/env/out, so it is safe this early.
  mode = UI::Mode.resolve(
    flags: {plain: plain, json: json, no_color: no_color, quiet: quiet},
    env: CLI.env,
    out: out
  )
  # Every line of --json output must parse as one JSON object.
  human = mode.format != :json

  if repo
    target = scope_target(repo)
    return fail_with(self, "invalid repo reference: #{repo.inspect} (expected host/owner/name)") if target.failure?

    match = target.success
    found = config.repos.find { |r| Repo::Helpers.same_repo?(r, match) }
    if found.nil?
      return fail_with(self, "no such tracked repo: #{Repo::Helpers.format_ref(match)}")
    end
    # Filtered config: only the one matched repo, no orgs
    # (org expansion would discover other repos — that's
    # exactly the G4 "other repo gets no state row" test
    # path, so we explicitly empty orgs here).
    config = Config::Store.with(config, repos: [found], orgs: [])
    out.puts "scoping sync to: #{Repo::Helpers.format_ref(found)}" if human
  end

  reporter = if mode.format == :json
    UI::JsonReporter.new(out)
  elsif mode.animate
    UI::InteractiveReporter.new(out, mode: mode)
  else
    UI::PlainReporter.new(out, mode: mode)
  end

  # prune only on a full sweep. With --repo the config above was
  # narrowed to one repo, so pruning would delete the state rows
  # for every OTHER tracked repo.
  result = Space::Src::Sync::Engine.new(reporter: reporter)
    .call(config: config, paths: paths, prune: repo.nil?)
  if result.failure?
    return fail_with(self, "sync failed: #{format_failure(result.failure)}")
  end

  report = result.success
  # JsonReporter already emits a run_finished event carrying the
  # summary, so suppressing the prose line loses nothing.
  out.puts "synced #{report.processed} repo(s)" if human
  CLI.record_outcome(Outcome.new(exit_code: 0))
end