Skip to content

Commit

Permalink
Add support for VSCodium
Browse files Browse the repository at this point in the history
VSCodium (https://vscodium.com/) is a community-driven, freely-licensed binary distribution of Microsoft’s editor VS Code.

The way extensions are exported and imported is the same as VSCode; There was a consideration to add version support (codium --list-extensions --show-versions) but it is not required as the IDE will update extensions itself
  • Loading branch information
helgi committed Jan 11, 2025
1 parent 60adb50 commit 9cb9735
Show file tree
Hide file tree
Showing 22 changed files with 268 additions and 57 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Bundler for non-Ruby dependencies from Homebrew, Homebrew Cask, Mac App Store, W

[Visual Studio Code](https://code.visualstudio.com/) is optional and used for installing Visual Studio Code extensions.

[VSCodium](https://vscodium.com/) is optional and used for installing VSCodium extensions.

## Installation

`brew bundle` is automatically installed when first run.
Expand Down Expand Up @@ -69,6 +71,9 @@ whalebrew "whalebrew/wget"

# 'vscode --install-extension'
vscode "GitHub.codespaces"

# 'codium --install-extension'
vscodium "GitHub.codespaces"
```

## Versions and lockfiles
Expand All @@ -78,7 +83,7 @@ If your software needs specific pinned versions, consider [`whalebrew`](https://

## New Installers/Checkers/Dumpers

`brew bundle` currently supports Homebrew, Homebrew Cask, Mac App Store, Whalebrew and Visual Studio Code.
`brew bundle` currently supports Homebrew, Homebrew Cask, Mac App Store, Whalebrew, Visual Studio Code, and VSCodium.

We are interested in contributions for other installers/checkers/dumpers but they must:

Expand Down
10 changes: 9 additions & 1 deletion cmd/bundle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ class BundleCmd < AbstractCommand
env: :bundle_dump_no_vscode,
description: "`dump` without VSCode extensions. " \
"This is enabled by default if `HOMEBREW_BUNDLE_DUMP_NO_VSCODE` is set."
switch "--vscodium",
description: "`list` or `dump` VSCodium extensions."

Check failure on line 99 in cmd/bundle.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Layout/ArgumentAlignment: Align the arguments of a method call if they span more than one line.

Check failure on line 99 in cmd/bundle.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Layout/ArgumentAlignment: Align the arguments of a method call if they span more than one line.
switch "--no-vscodium",
env: :bundle_dump_no_vscodium,

Check failure on line 101 in cmd/bundle.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Layout/ArgumentAlignment: Align the arguments of a method call if they span more than one line.

Check failure on line 101 in cmd/bundle.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Layout/ArgumentAlignment: Align the arguments of a method call if they span more than one line.
description: "`dump` without VSCodium extensions. " \
"This is enabled by default if `HOMEBREW_BUNDLE_DUMP_NO_VSCODIUM` is set."

Check failure on line 103 in cmd/bundle.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Layout/LineEndStringConcatenationIndentation: Align parts of a string concatenated with backslash.

Check failure on line 103 in cmd/bundle.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Layout/LineEndStringConcatenationIndentation: Align parts of a string concatenated with backslash.
switch "--describe",
env: :bundle_dump_describe,
description: "`dump` adds a description comment above each line, unless the " \
Expand All @@ -106,7 +112,9 @@ class BundleCmd < AbstractCommand
description: "`cleanup` casks using the `zap` command instead of `uninstall`."

conflicts "--all", "--no-vscode"
conflicts "--all", "--no-vscodium"
conflicts "--vscode", "--no-vscode"
conflicts "--vscodium", "--no-vscodium"

named_args %w[install dump cleanup check exec list]
end
Expand All @@ -129,7 +137,7 @@ def run
force = args.force?
zap = args.zap?

no_type_args = !args.brews? && !args.casks? && !args.taps? && !args.mas? && !args.whalebrew? && !args.vscode?
no_type_args = !args.brews? && !args.casks? && !args.taps? && !args.mas? && !args.whalebrew? && !args.vscode? && !args.vscodium?

Check failure on line 140 in cmd/bundle.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Layout/LineLength: Line is too long. [136/118]

Check failure on line 140 in cmd/bundle.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Layout/LineLength: Line is too long. [136/118]

case subcommand
when nil, "install"
Expand Down
3 changes: 3 additions & 0 deletions lib/bundle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@
require "bundle/vscode_extension_checker"
require "bundle/vscode_extension_dumper"
require "bundle/vscode_extension_installer"
require "bundle/vscodium_extension_checker"
require "bundle/vscodium_extension_dumper"
require "bundle/vscodium_extension_installer"
4 changes: 4 additions & 0 deletions lib/bundle/bundle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ def vscode_installed?
@vscode_installed ||= which("code").present?
end

def vscodium_installed?
@vscodium_installed ||= which("codium").present?
end

def whalebrew_installed?
@whalebrew_installed ||= which_formula("whalebrew")
end
Expand Down
20 changes: 14 additions & 6 deletions lib/bundle/checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@ def find_actionable(entries, exit_on_first_error: false, no_upgrade: false, verb
CheckResult = Struct.new :work_to_be_done, :errors

CHECKS = {
taps_to_tap: "Taps",
casks_to_install: "Casks",
extensions_to_install: "VSCode Extensions",
apps_to_install: "Apps",
formulae_to_install: "Formulae",
formulae_to_start: "Services",
taps_to_tap: "Taps",
casks_to_install: "Casks",
extensions_to_install: "VSCode Extensions",
vscodium_extensions_to_install: "VSCodium Extensions",
apps_to_install: "Apps",
formulae_to_install: "Formulae",
formulae_to_start: "Services",
}.freeze

def check(global: false, file: nil, exit_on_first_error: false, no_upgrade: false, verbose: false)
Expand Down Expand Up @@ -122,6 +123,13 @@ def extensions_to_install(exit_on_first_error: false, no_upgrade: false, verbose
)
end

def vscodium_extensions_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
Bundle::Checker::VscodiumExtensionChecker.new.find_actionable(
@dsl.entries,
exit_on_first_error:, no_upgrade:, verbose:,
)
end

def formulae_to_start(exit_on_first_error: false, no_upgrade: false, verbose: false)
Bundle::Checker::BrewServiceChecker.new.find_actionable(
@dsl.entries,
Expand Down
25 changes: 25 additions & 0 deletions lib/bundle/commands/cleanup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def reset!
Bundle::BrewDumper.reset!
Bundle::TapDumper.reset!
Bundle::VscodeExtensionDumper.reset!
Bundle::VscodiumExtensionDumper.reset!
Bundle::BrewServices.reset!
end

Expand All @@ -26,6 +27,7 @@ def run(global: false, file: nil, force: false, zap: false, dsl: nil)
formulae = formulae_to_uninstall(global:, file:)
taps = taps_to_untap(global:, file:)
vscode_extensions = vscode_extensions_to_uninstall(global:, file:)
vscodium_extensions = vscodium_extensions_to_uninstall(global:, file:)
if force
if casks.any?
args = zap ? ["--zap"] : []
Expand All @@ -44,6 +46,10 @@ def run(global: false, file: nil, force: false, zap: false, dsl: nil)
vscode_extensions.each do |extension|
Kernel.system "code", "--uninstall-extension", extension
end

vscodium_extensions.each do |extension|
Kernel.system "codium", "--uninstall-extension", extension
end
end

cleanup = system_output_no_stderr(HOMEBREW_BREW_FILE, "cleanup")
Expand Down Expand Up @@ -75,6 +81,12 @@ def run(global: false, file: nil, force: false, zap: false, dsl: nil)
would_uninstall = true
end

if vscodium_extensions.any?
puts "Would uninstall VSCodium extensions:"
puts Formatter.columns vscodium_extensions
would_uninstall = true
end

cleanup = system_output_no_stderr(HOMEBREW_BREW_FILE, "cleanup", "--dry-run")
unless cleanup.empty?
puts "Would `brew cleanup`:"
Expand Down Expand Up @@ -180,6 +192,19 @@ def vscode_extensions_to_uninstall(global: false, file: nil)
current_extensions - kept_extensions
end

def vscodium_extensions_to_uninstall(global: false, file: nil)
@dsl ||= Brewfile.read(global:, file:)
kept_extensions = @dsl.entries.select { |e| e.type == :vscodium }.map { |x| x.name.downcase }

# To provide a graceful migration from `Brewfile`s that don't yet or
# don't want to use `vscodium`: don't remove any extensions if we don't
# find any in the `Brewfile`.
return [].freeze if kept_extensions.empty?

current_extensions = Bundle::VscodiumExtensionDumper.extensions
current_extensions - kept_extensions
end

def system_output_no_stderr(cmd, *args)
IO.popen([cmd, *args], err: :close).read
end
Expand Down
4 changes: 2 additions & 2 deletions lib/bundle/commands/dump.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ module Commands
module Dump
module_function

def run(global:, file:, describe:, force:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:)
def run(global:, file:, describe:, force:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:, vscodium:)

Check failure on line 8 in lib/bundle/commands/dump.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Layout/LineLength: Line is too long. [122/118]

Check failure on line 8 in lib/bundle/commands/dump.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Layout/LineLength: Line is too long. [122/118]
Bundle::Dumper.dump_brewfile(
global:, file:, describe:, force:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:,
global:, file:, describe:, force:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:, vscodium:,
)
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/bundle/commands/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ module Commands
module List
module_function

def run(global:, file:, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
def run(global:, file:, brews:, casks:, taps:, mas:, whalebrew:, vscode:, vscodium:)
parsed_entries = Brewfile.read(global:, file:).entries
Bundle::Lister.list(
parsed_entries,
brews:, casks:, taps:, mas:, whalebrew:, vscode:,
brews:, casks:, taps:, mas:, whalebrew:, vscode:, vscodium:,
)
end
end
Expand Down
6 changes: 6 additions & 0 deletions lib/bundle/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ def vscode(name)
@entries << Entry.new(:vscode, name)
end

def vscodium(name)
raise "name(#{name.inspect}) should be a String object" unless name.is_a? String

@entries << Entry.new(:vscodium, name)
end

def tap(name, clone_target = nil, options = {})
raise "name(#{name.inspect}) should be a String object" unless name.is_a? String
if clone_target && !clone_target.is_a?(String)
Expand Down
7 changes: 4 additions & 3 deletions lib/bundle/dumper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,23 @@ def can_write_to_brewfile?(brewfile_path, force: false)
true
end

def build_brewfile(describe:, no_restart:, brews:, taps:, casks:, mas:, whalebrew:, vscode:)
def build_brewfile(describe:, no_restart:, brews:, taps:, casks:, mas:, whalebrew:, vscode:, vscodium:)
content = []
content << TapDumper.dump if taps
content << BrewDumper.dump(describe:, no_restart:) if brews
content << CaskDumper.dump(describe:) if casks
content << MacAppStoreDumper.dump if mas
content << WhalebrewDumper.dump if whalebrew
content << VscodeExtensionDumper.dump if vscode
content << VscodiumExtensionDumper.dump if vscodium
"#{content.reject(&:empty?).join("\n")}\n"
end

def dump_brewfile(global:, file:, describe:, force:, no_restart:, brews:, taps:, casks:, mas:, whalebrew:,
vscode:)
vscode:, vscodium:)
path = brewfile_path(global:, file:)
can_write_to_brewfile?(path, force:)
content = build_brewfile(describe:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:)
content = build_brewfile(describe:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:, vscodium:)
write_file path, content
end

Expand Down
4 changes: 3 additions & 1 deletion lib/bundle/installer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# frozen_string_literal: true
1# frozen_string_literal: true

Check failure on line 1 in lib/bundle/installer.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Lint/Void: Literal `1` used in void context.

Check failure on line 1 in lib/bundle/installer.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Style/FrozenStringLiteralComment: Missing frozen string literal comment.

Check failure on line 1 in lib/bundle/installer.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Layout/SpaceBeforeComment: Put a space before an end-of-line comment.

Check failure on line 1 in lib/bundle/installer.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Lint/Void: Literal `1` used in void context.

Check failure on line 1 in lib/bundle/installer.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Style/FrozenStringLiteralComment: Missing frozen string literal comment.

Check failure on line 1 in lib/bundle/installer.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Layout/SpaceBeforeComment: Put a space before an end-of-line comment.

module Bundle
module Installer
Expand Down Expand Up @@ -31,6 +31,8 @@ def install(entries, global: false, file: nil, no_lock: false, no_upgrade: false
Bundle::WhalebrewInstaller
when :vscode
Bundle::VscodeExtensionInstaller
when :vscodium
Bundle::VscodiumExtensionInstaller
when :tap
verb = "Tapping"
options = entry.options
Expand Down
7 changes: 4 additions & 3 deletions lib/bundle/lister.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ module Bundle
module Lister
module_function

def list(entries, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
def list(entries, brews:, casks:, taps:, mas:, whalebrew:, vscode:, vscodium:)
entries.each do |entry|
puts entry.name if show?(entry.type, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
puts entry.name if show?(entry.type, brews:, casks:, taps:, mas:, whalebrew:, vscode:, vscodium:)
end
end

def self.show?(type, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
def self.show?(type, brews:, casks:, taps:, mas:, whalebrew:, vscode:, vscodium:)
return true if brews && type == :brew
return true if casks && type == :cask
return true if taps && type == :tap
return true if mas && type == :mas
return true if whalebrew && type == :whalebrew
return true if vscode && type == :vscode
return true if vscodium && type == :vscodium

false
end
Expand Down
18 changes: 18 additions & 0 deletions lib/bundle/vscodium_extension_checker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Bundle
module Checker
class VscodiumExtensionChecker < Bundle::Checker::Base
PACKAGE_TYPE = :vscodium
PACKAGE_TYPE_NAME = "VSCodium Extension"

def failure_reason(extension, no_upgrade:)
"#{PACKAGE_TYPE_NAME} #{extension} needs to be installed."
end

def installed_and_up_to_date?(extension, no_upgrade: false)
Bundle::VscodiumExtensionInstaller.extension_installed?(extension)
end
end
end
end
25 changes: 25 additions & 0 deletions lib/bundle/vscodium_extension_dumper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Bundle
module VscodiumExtensionDumper
module_function

def reset!
@extensions = nil
end

def extensions
@extensions ||= if Bundle.vscodium_installed?
Bundle.exchange_uid_if_needed! do
`codium --list-extensions 2>/dev/null`
end.split("\n").map(&:downcase)
else
[]
end
end

def dump
extensions.map { |name| "vscodium \"#{name}\"" }.join("\n")
end
end
end
50 changes: 50 additions & 0 deletions lib/bundle/vscodium_extension_installer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

module Bundle
module VscodiumExtensionInstaller
module_function

def reset!
@installed_extensions = nil
end

def preinstall(name, no_upgrade: false, verbose: false)
if !Bundle.vscodium_installed? && Bundle.cask_installed?
puts "Installing vscodium. It is not currently installed." if verbose
Bundle.system HOMEBREW_BREW_FILE, "install", "--cask", "vscodium", verbose:
end

if extension_installed?(name)
puts "Skipping install of #{name} VSCodium extension. It is already installed." if verbose
return false
end

raise "Unable to install #{name} VSCodium extension. VSCodium is not installed." unless Bundle.vscodium_installed?

Check failure on line 22 in lib/bundle/vscodium_extension_installer.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Style/IfUnlessModifier: Modifier form of `unless` makes the line too long.

Check failure on line 22 in lib/bundle/vscodium_extension_installer.rb

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

Layout/LineLength: Line is too long. [120/118]

Check failure on line 22 in lib/bundle/vscodium_extension_installer.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Style/IfUnlessModifier: Modifier form of `unless` makes the line too long.

Check failure on line 22 in lib/bundle/vscodium_extension_installer.rb

View workflow job for this annotation

GitHub Actions / tests (macOS-latest)

Layout/LineLength: Line is too long. [120/118]

true
end

def install(name, preinstall: true, no_upgrade: false, verbose: false, force: false)
return true unless preinstall
return true if extension_installed?(name)

puts "Installing #{name} VSCodium extension. It is not currently installed." if verbose

return false unless Bundle.exchange_uid_if_needed! do
Bundle.system("codium", "--install-extension", name, verbose:)
end

installed_extensions << name

true
end

def extension_installed?(name)
installed_extensions.include? name.downcase
end

def installed_extensions
@installed_extensions ||= Bundle::VscodiumExtensionDumper.extensions
end
end
end
8 changes: 8 additions & 0 deletions spec/bundle/commands/check_command_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,14 @@
receive(:exit_early_check).once.and_call_original
expect { do_check }.to raise_error(SystemExit)
end

it "stops checking after the first VSCodium extension" do
allow_any_instance_of(Pathname).to receive(:read).and_return("vscodium 'abc'\nvscodium 'def'")

expect_any_instance_of(Bundle::Checker::VscodiumExtensionChecker).to \
receive(:exit_early_check).once.and_call_original
expect { do_check }.to raise_error(SystemExit)
end
end

context "when a new checker fails to implement installed_and_up_to_date" do
Expand Down
Loading

0 comments on commit 9cb9735

Please sign in to comment.