Skip to content

Commit 7994816

Browse files
authored
Merge pull request #22395 from Homebrew/bundle-cleanup-extensions
bundle: extend cleanup coverage
2 parents 9303785 + 2dad6dc commit 7994816

8 files changed

Lines changed: 156 additions & 5 deletions

File tree

Library/Homebrew/bundle/extensions/extension.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,11 @@ def self.cleanup_items(entries)
238238
installed_names - kept_packages
239239
end
240240

241+
sig { params(item: String).returns(String) }
242+
def self.cleanup_item_name(item)
243+
item
244+
end
245+
241246
sig { returns(Symbol) }
242247
def self.legacy_check_step
243248
:registered_extensions_to_install

Library/Homebrew/bundle/extensions/krew.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ def reset!
1818
@package_manager_executable = T.let(nil, T.nilable(Pathname))
1919
end
2020

21+
sig { override.returns(T.nilable(String)) }
22+
def cleanup_heading
23+
banner_name
24+
end
25+
2126
sig { override.returns(T.nilable(Pathname)) }
2227
def package_manager_executable
2328
@package_manager_executable ||= T.let(which("kubectl-krew", ORIGINAL_PATHS), T.nilable(Pathname))
@@ -71,6 +76,11 @@ def parse_plugin_list(output)
7176
end.uniq
7277
end
7378
private :parse_plugin_list
79+
80+
sig { override.params(name: String, executable: Pathname).void }
81+
def uninstall_package!(name, executable: Pathname.new(""))
82+
Bundle.system(executable.to_s, "uninstall", name, verbose: false)
83+
end
7484
end
7585
end
7686
end

Library/Homebrew/bundle/extensions/mac_app_store.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# frozen_string_literal: true
33

44
require "bundle/extensions/extension"
5+
require "json"
56

67
module Homebrew
78
module Bundle
@@ -32,6 +33,11 @@ def dump_disable_supported?
3233
false
3334
end
3435

36+
sig { override.returns(T.nilable(String)) }
37+
def cleanup_heading
38+
"Mac App Store apps"
39+
end
40+
3541
sig { override.params(name: String, options: Homebrew::Bundle::EntryInputOptions).returns(Dsl::Entry) }
3642
def entry(name, options = {})
3743
id = options[:id]
@@ -104,6 +110,41 @@ def dump_entry(package)
104110
"mas #{quote(app.name)}, id: #{app.id}"
105111
end
106112

113+
sig { params(app: App).returns(String) }
114+
def cleanup_item(app)
115+
JSON.generate("id" => app.id, "name" => app.name)
116+
end
117+
118+
sig { override.params(item: String).returns(String) }
119+
def cleanup_item_name(item)
120+
app = parse_cleanup_item(item)
121+
"#{app.name} (#{app.id})"
122+
end
123+
124+
sig { override.params(entries: T::Array[Dsl::Entry]).returns(T::Array[String]) }
125+
def cleanup_items(entries)
126+
return [].freeze unless package_manager_installed?
127+
128+
kept_app_ids = entries.filter_map do |entry|
129+
entry.options[:id].to_s if entry.type == type
130+
end
131+
return [].freeze if kept_app_ids.empty?
132+
133+
packages.reject { |app| kept_app_ids.any? { |id| app.id.to_i == id.to_i } }
134+
.map { |app| cleanup_item(app) }
135+
end
136+
137+
sig { override.params(items: T::Array[String]).void }
138+
def cleanup!(items)
139+
mas = package_manager_executable
140+
return if mas.nil?
141+
142+
items.each do |item|
143+
Bundle.system(mas, "uninstall", parse_cleanup_item(item).id, verbose: false)
144+
end
145+
puts "Uninstalled #{items.size} Mac App Store app#{"s" if items.size != 1}"
146+
end
147+
107148
sig { params(id: Integer).returns(T::Boolean) }
108149
def app_id_installed?(id)
109150
installed_app_ids.any? { |app_id| app_id.to_i == id }
@@ -209,6 +250,18 @@ def install!(name, id = nil, with: nil, preinstall: true, no_upgrade: false, ver
209250
installed_app_ids << id.to_s unless installed_app_ids.include?(id.to_s)
210251
true
211252
end
253+
254+
sig { params(item: String).returns(App) }
255+
def parse_cleanup_item(item)
256+
parsed = JSON.parse(item)
257+
raise TypeError, "Invalid Mac App Store cleanup item: #{item}" unless parsed.is_a?(Hash)
258+
259+
id = parsed["id"]
260+
name = parsed["name"]
261+
raise TypeError, "Invalid Mac App Store cleanup item: #{item}" if !id.is_a?(String) || !name.is_a?(String)
262+
263+
App.new(id:, name:)
264+
end
212265
end
213266

214267
sig { override.params(entries: T::Array[Object]).returns(T::Array[Object]) }

Library/Homebrew/bundle/subcommand/cleanup.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class CleanupSubcommand < Homebrew::AbstractSubcommand
2626
description: "Run `install` before cleaning up dependencies."
2727
switch "-f", "--force",
2828
description: "Actually perform cleanup operations."
29+
switch "--all",
30+
description: "Clean up all supported dependencies."
2931
switch "--formula", "--formulae", "--brews",
3032
description: "Clean up Homebrew formula dependencies."
3133
switch "--cask", "--casks",
@@ -47,13 +49,13 @@ def run
4749
file: context.file,
4850
force: context.force,
4951
zap: context.zap,
50-
formulae: args.formulae? || context.no_type_args,
51-
casks: args.casks? || context.no_type_args,
52-
taps: args.taps? || context.no_type_args,
52+
formulae: args.formulae? || args.all? || context.no_type_args,
53+
casks: args.casks? || args.all? || context.no_type_args,
54+
taps: args.taps? || args.all? || context.no_type_args,
5355
extension_types: context.extensions.select(&:cleanup_supported?).to_h do |extension|
5456
[
5557
extension.type,
56-
context.extension_selected?(args, extension) || context.no_type_args,
58+
context.extension_selected?(args, extension) || args.all? || context.no_type_args,
5759
]
5860
end,
5961
)
@@ -166,7 +168,7 @@ def self.cleanup(global: false, file: nil, force: false, zap: false, dsl: nil,
166168
next if items.empty?
167169

168170
puts "Would uninstall #{extension.cleanup_heading}:"
169-
puts Formatter.columns items
171+
puts Formatter.columns items.map { |item| extension.cleanup_item_name(item) }
170172
would_uninstall = true
171173
end
172174

Library/Homebrew/test/bundle/krew_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,32 @@
128128
end
129129
end
130130
end
131+
132+
describe "cleanup" do
133+
before do
134+
klass.reset!
135+
allow(klass).to receive_messages(
136+
package_manager_executable: Pathname.new("/usr/local/bin/kubectl-krew"),
137+
package_manager_installed?: true,
138+
packages: %w[ctx ns neat],
139+
installed_packages: %w[ctx ns neat],
140+
)
141+
end
142+
143+
it "returns plugins not in Brewfile entries" do
144+
entries = [Homebrew::Bundle::Dsl::Entry.new(:krew, "ctx")]
145+
expect(klass.cleanup_items(entries)).to eql(%w[ns neat])
146+
end
147+
148+
it "uninstalls plugins" do
149+
expect(Homebrew::Bundle).to receive(:system) do |*args, verbose:|
150+
expect(ENV.fetch("PATH", "")).to start_with("/usr/local/bin:")
151+
expect(args).to eq(["/usr/local/bin/kubectl-krew", "uninstall", "ns"])
152+
expect(verbose).to be(false)
153+
true
154+
end
155+
156+
expect { klass.cleanup!(["ns"]) }.to output(/Uninstalled 1 Krew plugin/).to_stdout
157+
end
158+
end
131159
end

Library/Homebrew/test/bundle/mac_app_store_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,29 @@
277277
end
278278
end
279279
end
280+
281+
describe "cleanup" do
282+
before do
283+
klass.reset!
284+
allow(klass).to receive_messages(package_manager_executable: Pathname.new("mas"), packages: [
285+
klass::App.new(id: "123", name: "foo"),
286+
klass::App.new(id: "456", name: "bar"),
287+
])
288+
end
289+
290+
it "returns apps not in Brewfile entries by ID" do
291+
entries = [Homebrew::Bundle::Dsl::Entry.new(:mas, "renamed foo", id: 123)]
292+
items = klass.cleanup_items(entries)
293+
294+
expect(items.map { |item| klass.cleanup_item_name(item) }).to eql(["bar (456)"])
295+
end
296+
297+
it "uninstalls apps by ID" do
298+
items = klass.cleanup_items([Homebrew::Bundle::Dsl::Entry.new(:mas, "foo", id: 123)])
299+
expect(Homebrew::Bundle).to receive(:system).with(Pathname("mas"), "uninstall", "456", verbose: false)
300+
.and_return(true)
301+
302+
expect { klass.cleanup!(items) }.to output(/Uninstalled 1 Mac App Store app/).to_stdout
303+
end
304+
end
280305
end

Library/Homebrew/test/cmd/bundle/cleanup_subcommand_spec.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,33 @@
88
RSpec.describe Homebrew::Cmd::Bundle::CleanupSubcommand do
99
let(:klass) { Homebrew::Cmd::Bundle::CleanupSubcommand }
1010

11+
describe "#run" do
12+
it "cleans up every supported type when --all is passed" do
13+
args = args_for_subcommand(:cleanup, all?: true, formulae?: false, casks?: false, taps?: false, mas?: false,
14+
vscode?: false, cargo?: false, flatpak?: false, go?: false, krew?: false,
15+
npm?: false, uv?: false)
16+
context = bundle_subcommand_context(:cleanup, no_type_args: false)
17+
18+
expect(klass).to receive(:cleanup) do |formulae:, casks:, taps:, extension_types:, **|
19+
expect(formulae).to be(true)
20+
expect(casks).to be(true)
21+
expect(taps).to be(true)
22+
expect(extension_types).to include(
23+
cargo: true,
24+
flatpak: true,
25+
go: true,
26+
krew: true,
27+
mas: true,
28+
npm: true,
29+
uv: true,
30+
vscode: true,
31+
)
32+
end
33+
34+
klass.new(args, context:).run
35+
end
36+
end
37+
1138
describe "read Brewfile and current installation", :no_api do
1239
before do
1340
klass.reset!

Library/Homebrew/test/cmd/bundle_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
expect(subcommand_options.call("list")["--vscode"]).to eq("List VSCode (and forks/variants) extensions.")
4545
expect(subcommand_options.call("dump")["--vscode"]).to eq("Dump VSCode (and forks/variants) extensions.")
4646
expect(subcommand_options.call("cleanup")["--vscode"]).to eq("Clean up VSCode (and forks/variants) extensions.")
47+
expect(subcommand_options.call("cleanup")["--all"]).to eq("Clean up all supported dependencies.")
4748
expect(subcommand_options.call("add")["--vscode"])
4849
.to eq("Add entries for VSCode (and forks/variants) extensions.")
4950
expect(subcommand_options.call("remove")["--vscode"])

0 commit comments

Comments
 (0)