Class: ToggleLocalSpm::CLI

Inherits:
Object
  • Object
show all
Defined in:
lib/toggle_local_spm/cli.rb

Overview

Toggles one or more Swift Package Manager dependencies between their remote (git) reference and a local checkout. Run again on the same package to swap back to the original remote reference. Afterwards, optionally runs xcodebuild -resolvePackageDependencies so Package.resolved is updated to match (requires network access and, for private repos, a working SSH key).

Run from the root of the Xcode project's repo (the directory containing the .xcodeproj). A spm-local-overrides.json file is kept at the repo root as a permanent record of each package's remote reference and local checkout path (see README).

Packages come in two flavors:

  • "direct": already has (or had) a top-level XCRemoteSwiftPackageReference/ XCLocalSwiftPackageReference entry in project.pbxproj. Toggling off always leaves a reference behind (swapped back to remote) since the project genuinely depends on it.
  • "indirect": only known via Package.resolved — a transitive dependency of one of the project's own direct dependencies (e.g. a package declared in another package's own Package.swift), with no top-level reference of its own. Toggling one of these on adds a brand-new, unlinked local reference (no product/target linkage) purely so SwiftPM's identity-based graph resolution overrides the transitive pin with the local checkout; toggling off removes that reference entirely, since it was never a real project dependency.

Which of these two states a package is in is recorded as "type" in spm-local-overrides.json. Whether it's currently local or remote is always read from the live project (an existing ref's isa, or its absence for a not-yet-toggled indirect package) — never from the state file or from Package.resolved, which can lag behind.

Defined Under Namespace

Classes: Candidate

Constant Summary collapse

RemoteRef =
Xcodeproj::Project::Object::XCRemoteSwiftPackageReference
LocalRef =
Xcodeproj::Project::Object::XCLocalSwiftPackageReference
ProductDependency =
Xcodeproj::Project::Object::XCSwiftPackageProductDependency
TYPE_LABELS =
{ "direct" => "🎯 direct", "indirect" => "🧩 indirect" }.freeze
STATE_LABELS =
{ local: "📁 local", remote: "🌏 remote" }.freeze
MANAGED_LABELS =
{ set: "", found: "📁" }.freeze
MANAGED_LEGEND =
[
  "#{MANAGED_LABELS[:set]}  Local mock set",
  "#{MANAGED_LABELS[:found]}  Mock not set. Mock found (recorded in spm-local-overrides.json)",
  "   Blank — no record for this dependency in spm-local-overrides.json"
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv, root: Dir.pwd) ⇒ CLI

Returns a new instance of CLI.



64
65
66
67
68
# File 'lib/toggle_local_spm/cli.rb', line 64

def initialize(argv, root: Dir.pwd)
  @package_names = argv
  @root = Pathname.new(root).expand_path
  @state_file = @root + "spm-local-overrides.json"
end

Class Method Details

.run(argv) ⇒ Object



60
61
62
# File 'lib/toggle_local_spm/cli.rb', line 60

def self.run(argv)
  new(argv).run
end

Instance Method Details

#runObject



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/toggle_local_spm/cli.rb', line 70

def run
  xcodeproj_path = find_xcodeproj_path
  project = Xcodeproj::Project.open(xcodeproj_path)
  state = load_state

  candidates = build_candidates(project, xcodeproj_path, state)
  abort_with("no Swift package dependencies found") if candidates.empty?

  selected = select_candidates(candidates)

  check_xcode_running

  selected.each { |candidate| toggle(project, xcodeproj_path, candidate, state) }

  save_state(state)
  project.save

  resolve_package_dependencies(xcodeproj_path) if prompt_resolve?
  open_in_xcode(xcodeproj_path) if prompt_open_in_xcode?

  puts "Done."
end