diff options
| author | William Boman <william@redwill.se> | 2026-05-22 20:14:45 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-22 20:14:45 +0200 |
| commit | 24e77d5289db0f7b6f4b405683047315b66dc75b (patch) | |
| tree | 1d40c97d129c8d621d9c9d0645c7c268b49505b2 | |
| parent | feat(registry): add registry_cache setting for controlling cache behaviour (#... (diff) | |
| download | mason-24e77d5289db0f7b6f4b405683047315b66dc75b.tar mason-24e77d5289db0f7b6f4b405683047315b66dc75b.tar.gz mason-24e77d5289db0f7b6f4b405683047315b66dc75b.tar.bz2 mason-24e77d5289db0f7b6f4b405683047315b66dc75b.tar.lz mason-24e77d5289db0f7b6f4b405683047315b66dc75b.tar.xz mason-24e77d5289db0f7b6f4b405683047315b66dc75b.tar.zst mason-24e77d5289db0f7b6f4b405683047315b66dc75b.zip | |
feat: add support for socket.dev firewall client (#2088)
22 files changed, 434 insertions, 16 deletions
@@ -28,6 +28,7 @@ - [Commands](#commands) - [Registries](#registries) - [Screenshots](#screenshots) +- [Firewall (socket.dev)](#firewall-socketdev) - [Configuration](#configuration) ## Introduction @@ -134,6 +135,39 @@ functions to ensure you have the latest package information before retrieving pa | <img alt="Main window" src="https://github.com/user-attachments/assets/b9a57d21-f551-45ad-a1e5-a9fd66291510"> | <img alt="Language search" src="https://github.com/user-attachments/assets/3d24fb7b-2c57-4948-923b-0a42bb627cbe"> | <img alt="Language filter" src="https://github.com/user-attachments/assets/c0ca5818-3c74-4071-bc41-427a2cd1056d"> | | <img alt="Package information" src="https://github.com/user-attachments/assets/6f9f6819-ac97-483d-a77c-8f6c6131ac85"> | <img alt="New package versions" src="https://github.com/user-attachments/assets/ff1adc4d-2fcc-46df-ab4c-291c891efa50"> | <img alt="Help window" src="https://github.com/user-attachments/assets/1fbe75e4-fe69-4417-83e3-82329e1c236e"> | +## Firewall (socket.dev) + +> Socket Firewall is a free tool that blocks malicious packages at install time, giving developers proactive protection +> against rising supply chain attacks. + +`mason.nvim` supports the [Socket.dev firewall](https://socket.dev/). To enable the firewall, turn it on in the configuration: + +```lua +require("mason").setup { + firewall = { + enabled = true + } +} +``` + +By default, `mason.nvim` will automatically install and update the Socket Firewall client. If you want to manage the +client manually, set `auto_managed` to `false` (this requires the `sfw` binary to be available in your `PATH`): + +```lua +require("mason").setup { + firewall = { + enabled = true, + auto_managed = false + } +} +``` + +For more information refer to the [Socket.dev](https://socket.dev/) documentation. + +> [!NOTE] +> If you already use the Socket.dev firewall in a proxy service configuration you don't need to enable the firewall in +> `mason.nvim`. + ## Configuration > [`:h mason-settings`][help-mason-settings] @@ -215,6 +249,31 @@ local DEFAULT_SETTINGS = { "github:mason-org/mason-system-registry", }, + registry_cache = { + ---@since 2.3.0 + -- [Advanced setting] + -- Whether Mason should automatically refresh the registry when needed. If false, the registry will have to be + -- updated manually via :MasonUpdate or the :Mason UI. + refresh = true, + + ---@since 2.3.0 + -- Amount of seconds before the local registry cache is considered stale. + -- Note that this setting has no effect if refresh is set to false. + duration = 24 * 60 * 60, -- 24 hours + }, + + firewall = { + ---@since 2.3.0 + -- Whether to enable the socket.dev firewall (sfw) for supported package sources. + -- For more information, refer to https://socket.dev. + enabled = false, + + ---@since 2.3.0 + -- Whether mason.nvim should automatically install and update the Socket Firewall client. + -- If false, the sfw binary must exist in PATH if the firewall is enabled. + auto_managed = true, + }, + ---@since 1.0.0 -- The provider implementations to use for resolving supplementary package metadata (e.g., all available versions). -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail. diff --git a/doc/mason.txt b/doc/mason.txt index e751a94b..2082f62a 100644 --- a/doc/mason.txt +++ b/doc/mason.txt @@ -137,6 +137,33 @@ See also ~ Launch background jobs: |jobstart| & |uv.spawn()| (via |vim.loop|) ============================================================================== +MASON FIREWALL *mason-firewall* + +`mason.nvim` supports the Socket.dev firewall. To enable the firewall, turn it +on in the configuration: +>lua + require("mason").setup { + firewall = { + enabled = true + } + } +< + +By default, `mason.nvim` will automatically install and update the Socket +Firewall client. If you want to manage the client manually, set `auto_managed` +to `false` (this requires the `sfw` binary to be available in your `PATH`): +>lua + require("mason").setup { + firewall = { + enabled = true, + auto_managed = false + } + } +< + +For more information refer to the documentation at https://socket.dev/. + +============================================================================== COMMANDS *mason-commands* ------------------------------------------------------------------------------ @@ -258,6 +285,31 @@ Example: "github:mason-org/mason-system-registry", }, + registry_cache = { + ---@since 2.3.0 + -- [Advanced setting] + -- Whether Mason should automatically refresh the registry when needed. If false, the registry will have to be + -- updated manually via :MasonUpdate or the :Mason UI. + refresh = true, + + ---@since 2.3.0 + -- Amount of seconds before the local registry cache is considered stale. + -- Note that this setting has no effect if refresh is set to false. + duration = 24 * 60 * 60, -- 24 hours + }, + + firewall = { + ---@since 2.3.0 + -- Whether to enable the socket.dev firewall (sfw) for supported package sources. + -- For more information, refer to https://socket.dev. + enabled = false, + + ---@since 2.3.0 + -- Whether mason.nvim should automatically install and update the Socket Firewall client. + -- If false, the sfw binary must exist in PATH if the firewall is enabled. + auto_managed = true, + }, + ---@since 1.0.0 -- The provider implementations to use for resolving supplementary package metadata (e.g., all available versions). -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail. diff --git a/lua/mason-core/async/init.lua b/lua/mason-core/async/init.lua index 03963264..e3a2e850 100644 --- a/lua/mason-core/async/init.lua +++ b/lua/mason-core/async/init.lua @@ -78,7 +78,8 @@ local function new_execution_context(suspend_fn, callback, ...) if cancelled or not thread then return end - local ok, promise_or_result = co.resume(thread, ...) + local results = { co.resume(thread, ...) } + local ok, promise_or_result = results[1], results[2] if cancelled or not thread then return end @@ -88,7 +89,7 @@ local function new_execution_context(suspend_fn, callback, ...) promise_or_result(step) else -- yield to parent coroutine - step(coroutine.yield(promise_or_result)) + step(coroutine.yield(promise_or_result, unpack(results, 3))) end else callback(true, promise_or_result) diff --git a/lua/mason-core/installer/InstallHandle.lua b/lua/mason-core/installer/InstallHandle.lua index 3846659e..6492acd9 100644 --- a/lua/mason-core/installer/InstallHandle.lua +++ b/lua/mason-core/installer/InstallHandle.lua @@ -20,6 +20,7 @@ local uv = vim.loop ---@field pid integer ---@field cmd string ---@field args string[] +---@field firewall boolean local InstallHandleSpawnHandle = {} InstallHandleSpawnHandle.__index = InstallHandleSpawnHandle @@ -27,7 +28,8 @@ InstallHandleSpawnHandle.__index = InstallHandleSpawnHandle ---@param pid integer ---@param cmd string ---@param args string[] -function InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args) +---@param firewall boolean +function InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args, firewall) ---@type InstallHandleSpawnHandle local instance = {} setmetatable(instance, InstallHandleSpawnHandle) @@ -35,6 +37,7 @@ function InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args) instance.pid = pid instance.cmd = cmd instance.args = args + instance.firewall = firewall return instance end @@ -73,8 +76,9 @@ end ---@param pid integer ---@param cmd string ---@param args string[] -function InstallHandle:register_spawn_handle(luv_handle, pid, cmd, args) - local spawn_handles = InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args) +---@param firewall boolean +function InstallHandle:register_spawn_handle(luv_handle, pid, cmd, args, firewall) + local spawn_handles = InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args, firewall) log.fmt_trace("Pushing spawn_handles stack for %s: %s (pid: %s)", self, spawn_handles, pid) self.spawn_handles[#self.spawn_handles + 1] = spawn_handles self:emit "spawn_handles:change" diff --git a/lua/mason-core/installer/InstallRunner.lua b/lua/mason-core/installer/InstallRunner.lua index 3eba879a..336f0c93 100644 --- a/lua/mason-core/installer/InstallRunner.lua +++ b/lua/mason-core/installer/InstallRunner.lua @@ -39,7 +39,14 @@ function InstallRunner:execute(opts, callback) local handle = self.handle log.fmt_info("Executing installer for %s %s", handle.package, opts) - local context = InstallContext:new(handle, opts) + local context = InstallContext:new(handle, opts, { + suspend = function() + self:suspend() + end, + resume = function() + self:resume() + end, + }) local tailed_output = {} @@ -212,6 +219,20 @@ function InstallRunner:acquire_permit() return Result.success(channel) end +function InstallRunner:suspend() + if self.global_permit then + self.global_permit:forget() + self.global_permit = nil + end +end + +---@async +function InstallRunner:resume() + self.handle:set_state "QUEUED" + self.global_permit = self.global_semaphore:acquire() + self.handle:set_state "ACTIVE" +end + ---@private function InstallRunner:release_permit() if self.global_permit then diff --git a/lua/mason-core/installer/context/InstallContextSpawn.lua b/lua/mason-core/installer/context/InstallContextSpawn.lua index 29e62101..25419980 100644 --- a/lua/mason-core/installer/context/InstallContextSpawn.lua +++ b/lua/mason-core/installer/context/InstallContextSpawn.lua @@ -22,7 +22,7 @@ end ---@param cmd string function InstallContextSpawn:__index(cmd) - ---@param args JobSpawnOpts + ---@param args SpawnArgs return function(args) args.cwd = args.cwd or self.cwd:get() args.stdio_sink = args.stdio_sink or self.handle.stdio_sink @@ -30,7 +30,7 @@ function InstallContextSpawn:__index(cmd) local captured_handle args.on_spawn = function(handle, stdio, pid, ...) captured_handle = handle - self.handle:register_spawn_handle(handle, pid, cmd, spawn._flatten_cmd_args(args)) + self.handle:register_spawn_handle(handle, pid, cmd, spawn._flatten_cmd_args(args), args.firewall == true) if on_spawn then on_spawn(handle, stdio, pid, ...) end diff --git a/lua/mason-core/installer/context/init.lua b/lua/mason-core/installer/context/init.lua index 9225315f..69738911 100644 --- a/lua/mason-core/installer/context/init.lua +++ b/lua/mason-core/installer/context/init.lua @@ -21,19 +21,22 @@ local receipt = require "mason-core.receipt" ---@field cwd InstallContextCwd ---@field opts PackageInstallOpts ---@field stdio_sink StdioSink +---@field runner { suspend: fun(), resume: async fun() } ---@field links { bin: table<string, string>, share: table<string, string>, opt: table<string, string> } local InstallContext = {} InstallContext.__index = InstallContext ---@param handle InstallHandle ---@param opts PackageInstallOpts -function InstallContext:new(handle, opts) +---@param runner { suspend: fun(), resume: async fun() } +function InstallContext:new(handle, opts, runner) local cwd = InstallContextCwd:new(handle) local spawn = InstallContextSpawn:new(handle, cwd, false) local fs = InstallContextFs:new(cwd) return setmetatable({ cwd = cwd, spawn = spawn, + runner = runner, handle = handle, location = handle.location, -- for convenience package = handle.package, -- for convenience @@ -264,6 +267,7 @@ function InstallContext:link_bin(executable, rel_path) end InstallContext.CONTEXT_REQUEST = {} +InstallContext.ABORT = {} ---@generic T ---@param fn fun(context: InstallContext): T @@ -277,14 +281,17 @@ function InstallContext:execute(fn) local step local ret_val step = function(...) - local ok, result = coroutine.resume(thread, ...) + local results = { coroutine.resume(thread, ...) } + local ok, result = results[1], results[2] if not ok then error(result, 0) elseif result == InstallContext.CONTEXT_REQUEST then step(self) + elseif result == InstallContext.ABORT then + ret_val = Result.failure(results[3]) elseif coroutine.status(thread) == "suspended" then -- yield to parent coroutine - step(coroutine.yield(result)) + step(coroutine.yield(result, unpack(results, 3))) else ret_val = result end @@ -294,6 +301,31 @@ function InstallContext:execute(fn) end ---@async +---@param system_pkg SystemPackage +function InstallContext:require(system_pkg) + local result = Result.try(function(try) + if try(system_pkg:needs_install()) then + self.stdio_sink:stdout("Installing dependency " .. system_pkg.name .. ".\n") + self.runner.suspend() + try(system_pkg:install():on_failure(function() + if self.opts.force then + self.runner.resume() + end + end)) + self.runner.resume() + end + end) + if result:is_failure() then + if not self.opts.force then + self.stdio_sink:stderr "Run with :MasonInstall --force to attempt installation anyway.\n" + coroutine.yield(InstallContext.ABORT, result:err_or_nil()) + else + self.stdio_sink:stderr(result:err_or_nil() .. "\n") + end + end +end + +---@async function InstallContext:build_receipt() log.fmt_debug("Building receipt for %s", self.package) return Result.pcall(function() diff --git a/lua/mason-core/installer/managers/cargo.lua b/lua/mason-core/installer/managers/cargo.lua index 22ec9ed6..a6116f9a 100644 --- a/lua/mason-core/installer/managers/cargo.lua +++ b/lua/mason-core/installer/managers/cargo.lua @@ -1,4 +1,5 @@ local Result = require "mason-core.result" +local SystemPackage = require "mason-core.system-package" local _ = require "mason-core.functional" local installer = require "mason-core.installer" local log = require "mason-core.log" @@ -15,6 +16,7 @@ function M.install(crate, version, opts) opts = opts or {} log.fmt_debug("cargo: install %s %s %s", crate, version, opts) local ctx = installer.context() + ctx:require(SystemPackage.sfw) ctx.stdio_sink:stdout(("Installing crate %s@%s…\n"):format(crate, version)) return ctx.spawn.cargo { "install", @@ -29,6 +31,7 @@ function M.install(crate, version, opts) opts.features and { "--features", opts.features } or vim.NIL, opts.locked and "--locked" or vim.NIL, crate, + firewall = true, } end diff --git a/lua/mason-core/installer/managers/npm.lua b/lua/mason-core/installer/managers/npm.lua index 93af3a85..8a8d1582 100644 --- a/lua/mason-core/installer/managers/npm.lua +++ b/lua/mason-core/installer/managers/npm.lua @@ -1,4 +1,5 @@ local Result = require "mason-core.result" +local SystemPackage = require "mason-core.system-package" local _ = require "mason-core.functional" local installer = require "mason-core.installer" local log = require "mason-core.log" @@ -62,12 +63,14 @@ function M.install(pkg, version, opts) opts = opts or {} log.fmt_debug("npm: install %s %s %s", pkg, version, opts) local ctx = installer.context() + ctx:require(SystemPackage.sfw) ctx.stdio_sink:stdout(("Installing npm package %s@%s…\n"):format(pkg, version)) return ctx.spawn.npm { "install", ("%s@%s"):format(pkg, version), opts.extra_packages or vim.NIL, opts.install_extra_args or vim.NIL, + firewall = true, } end diff --git a/lua/mason-core/installer/managers/pypi.lua b/lua/mason-core/installer/managers/pypi.lua index 9b39b0d6..a8efd0dd 100644 --- a/lua/mason-core/installer/managers/pypi.lua +++ b/lua/mason-core/installer/managers/pypi.lua @@ -1,5 +1,6 @@ local Optional = require "mason-core.optional" local Result = require "mason-core.result" +local SystemPackage = require "mason-core.system-package" local _ = require "mason-core.functional" local a = require "mason-core.async" local installer = require "mason-core.installer" @@ -173,6 +174,8 @@ end ---@param pkgs string[] ---@param extra_args? string[] local function pip_install(pkgs, extra_args) + local ctx = installer.context() + ctx:require(SystemPackage.sfw) return venv_python { "-m", "pip", @@ -182,6 +185,7 @@ local function pip_install(pkgs, extra_args) "--ignore-installed", extra_args or vim.NIL, pkgs, + firewall = true, } end diff --git a/lua/mason-core/result.lua b/lua/mason-core/result.lua index e98a11b3..27c46862 100644 --- a/lua/mason-core/result.lua +++ b/lua/mason-core/result.lua @@ -189,7 +189,8 @@ function Result.try(fn) local thread = coroutine.create(fn) local step step = function(...) - local ok, result = coroutine.resume(thread, ...) + local results = { coroutine.resume(thread, ...) } + local ok, result = results[1], results[2] if not ok then return Result.failure(result) end @@ -207,7 +208,7 @@ function Result.try(fn) end else -- yield to parent coroutine - return step(coroutine.yield(result)) + return step(coroutine.yield(result, unpack(results, 3))) end end return step(coroutine.yield) diff --git a/lua/mason-core/spawn.lua b/lua/mason-core/spawn.lua index 3c9c645d..78be4fd7 100644 --- a/lua/mason-core/spawn.lua +++ b/lua/mason-core/spawn.lua @@ -4,6 +4,7 @@ local a = require "mason-core.async" local log = require "mason-core.log" local platform = require "mason-core.platform" local process = require "mason-core.process" +local settings = require "mason.settings" local is_not_nil = _.complement(_.equals(vim.NIL)) @@ -64,6 +65,7 @@ local get_path_from_env_list = ---@field stdio_sink StdioSink? If provided, will be used to write to stdout and stderr. ---@field cwd string? ---@field on_spawn (fun(handle: luv_handle, stdio: luv_pipe[], pid: integer))? Will be called when the process successfully spawns. +---@field firewall boolean? setmetatable(spawn, { ---@param canonical_cmd string @@ -102,6 +104,20 @@ setmetatable(spawn, { end end + if args.firewall and settings.current.firewall.enabled then + a.scheduler() + table.insert(spawn_args.args, 1, cmd) + local expanded_cmd = exepath( + "sfw", + settings.current.firewall.auto_managed and vim.fn.expand "$MASON/opt/mason/system/bin" or nil + ) + if expanded_cmd == "" then + return Failure({ stderr = "Failed to find sfw (Socket Firewall) in PATH." }, "sfw") + else + cmd = expanded_cmd + end + end + local _, exit_code, signal = a.wait(function(resolve) local handle, stdio, pid = process.spawn(cmd, spawn_args, resolve) if args.on_spawn and handle and stdio and pid then diff --git a/lua/mason-core/system-package.lua b/lua/mason-core/system-package.lua new file mode 100644 index 00000000..798db774 --- /dev/null +++ b/lua/mason-core/system-package.lua @@ -0,0 +1,96 @@ +local Result = require "mason-core.result" +local _ = require "mason-core.functional" +local a = require "mason-core.async" +local registry = require "mason-registry" +local settings = require "mason.settings" +local OneShotChannel = require("mason-core.async.control").OneShotChannel + +---@class SystemPackage +---@field name string +---@field condition? fun(): bool +local SystemPackage = {} +SystemPackage.__index = SystemPackage + +---@type table<string, OneShotChannel> +SystemPackage.channels = {} + +function SystemPackage:new(system_pkg_name) + ---@type SystemPackage + local instance = {} + setmetatable(instance, self) + instance.name = system_pkg_name + return instance +end + +function SystemPackage:conditional(fn) + self.condition = fn + return self +end + +---@async +function SystemPackage:get_package() + a.scheduler() + pcall(a.wait, registry.refresh_system) + if not registry.has_system_package(self.name) then + -- Force update to the very latest registry version + pcall(a.wait, registry.update) + end + if not registry.has_system_package(self.name) then + return Result.failure("Unable to find system package " .. self.name) + end + return Result.pcall(registry.get_system_package, self.name) +end + +---@async +---@return Result<boolean> +function SystemPackage:needs_install() + return Result.try(function(try) + if self.condition and not self.condition() then + return false + end + local pkg = try(self:get_package()) + if not pkg:is_installed() or pkg:is_installing() then + return true + end + if pkg:get_installed_version() ~= pkg:get_latest_version() then + return true + end + return false + end) +end + +---@async +function SystemPackage:await_channel() + assert(SystemPackage.channels[self.name], "Tried to await non-existing channel.") + local success, result = SystemPackage.channels[self.name]:receive() + if not success then + return Result.failure("Failed to install system package " .. self.name .. ". Error: " .. result) + end + return Result.success() +end + +---@async +---@return Result +function SystemPackage:install() + return Result.try(function(try) + local pkg = try(self:get_package()) + if not pkg:is_installing() then + local channel = OneShotChannel:new() + SystemPackage.channels[self.name] = channel + pkg:install({}, function(success, result) + channel:send(success, result) + end) + end + return self:await_channel() + end) +end + +function SystemPackage:__tostring() + return ("SystemPackage(name=%s)"):format(self.name) +end + +SystemPackage.sfw = SystemPackage:new("sfw@latest"):conditional(function() + return settings.current.firewall.enabled and settings.current.firewall.auto_managed +end) + +return SystemPackage diff --git a/lua/mason-registry/init.lua b/lua/mason-registry/init.lua index 523a1256..167a3dfb 100644 --- a/lua/mason-registry/init.lua +++ b/lua/mason-registry/init.lua @@ -1,5 +1,6 @@ local EventEmitter = require "mason-core.EventEmitter" local InstallLocation = require "mason-core.installer.InstallLocation" +local _ = require "mason-core.functional" local log = require "mason-core.log" local path = require "mason-core.path" local settings = require "mason.settings" @@ -57,6 +58,11 @@ function Registry.get_system_package(pkg_name) error(("Cannot find system package %q."):format(pkg_name)) end +function Registry.has_system_package(pkg_name) + local ok = pcall(Registry.get_system_package, pkg_name) + return ok +end + function Registry.get_installed_package_names() local fs = require "mason-core.fs" if not fs.sync.dir_exists(InstallLocation.global():package()) then diff --git a/lua/mason-test/helpers.lua b/lua/mason-test/helpers.lua index 88354046..a056406e 100644 --- a/lua/mason-test/helpers.lua +++ b/lua/mason-test/helpers.lua @@ -12,7 +12,10 @@ local M = {} function M.create_context(opts) local pkg = registry.get_package(opts and opts.package or "dummy") local handle = InstallHandle:new(pkg, InstallLocation.global()) - local context = InstallContext:new(handle, opts and opts.install_opts or {}) + local context = InstallContext:new(handle, opts and opts.install_opts or {}, { + suspend = function() end, + resume = function() end, + }) context.spawn = setmetatable({}, { __index = function(s, cmd) s[cmd] = spy.new(function() diff --git a/lua/mason/settings.lua b/lua/mason/settings.lua index 1c72756b..33278bb7 100644 --- a/lua/mason/settings.lua +++ b/lua/mason/settings.lua @@ -55,6 +55,18 @@ local DEFAULT_SETTINGS = { duration = 24 * 60 * 60, -- 24 hours }, + firewall = { + ---@since 2.3.0 + -- Whether to enable the socket.dev firewall (sfw) for supported package sources. + -- For more information, refer to https://socket.dev. + enabled = false, + + ---@since 2.3.0 + -- Whether mason.nvim should automatically install and update the Socket Firewall client. + -- If false, the sfw binary must exist in PATH if the firewall is enabled. + auto_managed = true, + }, + ---@since 1.0.0 -- The provider implementations to use for resolving supplementary package metadata (e.g., all available versions). -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail. diff --git a/lua/mason/ui/components/main/package_list.lua b/lua/mason/ui/components/main/package_list.lua index 11f0d174..67805088 100644 --- a/lua/mason/ui/components/main/package_list.lua +++ b/lua/mason/ui/components/main/package_list.lua @@ -271,6 +271,7 @@ local function InstallingPackageComponent(pkg, state) pkg_state.has_failed and p.error(settings.current.ui.icons.package_uninstalled) or p.highlight(settings.current.ui.icons.package_pending), p.none(" " .. pkg.name), + pkg_state.firewall_active and p.highlight " (firewall active)" or p.none "", current_state, pkg_state.latest_spawn and p.Comment((" $ %s"):format(pkg_state.latest_spawn)) or p.none "", }, diff --git a/lua/mason/ui/instance.lua b/lua/mason/ui/instance.lua index 476bdf8c..fc035318 100644 --- a/lua/mason/ui/instance.lua +++ b/lua/mason/ui/instance.lua @@ -46,6 +46,7 @@ end ---@field is_log_expanded boolean ---@field has_failed boolean ---@field latest_spawn string? +---@field firewall_active boolean? ---@field linked_executables table<string, string>? ---@field installed_purl string? ---@field lsp_settings_schema table? @@ -239,8 +240,13 @@ local function setup_handle(handle) local function handle_spawnhandle_change() mutate_state(function(state) + local spawn_handle = handle:peek_spawn_handle() state.packages.states[handle.package.name].latest_spawn = - handle:peek_spawn_handle():map(tostring):map(_.gsub("\n", "\\n ")):or_else(nil) + spawn_handle:map(tostring):map(_.gsub("\n", "\\n ")):or_else(nil) + if settings.current.firewall.enabled then + state.packages.states[handle.package.name].firewall_active = + spawn_handle:map(_.prop "firewall"):or_else(false) + end end) end diff --git a/tests/mason-core/installer/context_spec.lua b/tests/mason-core/installer/context_spec.lua index 92ef1c49..5b0716c3 100644 --- a/tests/mason-core/installer/context_spec.lua +++ b/tests/mason-core/installer/context_spec.lua @@ -2,7 +2,6 @@ local a = require "mason-core.async" local match = require "luassert.match" local path = require "mason-core.path" local pypi = require "mason-core.installer.managers.pypi" -local registry = require "mason-registry" local spy = require "luassert.spy" local stub = require "luassert.stub" local test_helpers = require "mason-test.helpers" @@ -279,4 +278,91 @@ cmd.exe /C echo %GREETING% %*]] assert.equals("Error!", error) assert.spy(guard).was_called(0) end) + + describe("system packages", function() + local Result = require "mason-core.result" + local SystemPackage = require "mason-core.system-package" + local _ = require "mason-core.functional" + + local NeedsInstallSystemPackage = SystemPackage:new "needs-install" + NeedsInstallSystemPackage.needs_install = _.always(Result.success(true)) + NeedsInstallSystemPackage.install = _.always(Result.success()) + + local InstalledSystemPackage = SystemPackage:new "no-needs-install" + InstalledSystemPackage.needs_install = _.always(Result.success(false)) + InstalledSystemPackage.install = _.always(Result.success()) + + local FailingSystemPackage = SystemPackage:new "failing-install" + FailingSystemPackage.needs_install = _.always(Result.success(true)) + FailingSystemPackage.install = _.always(Result.failure "There was an issue.") + + it("should install required system packages", function() + local ctx = test_helpers.create_context() + + spy.on(ctx.runner, "suspend") + spy.on(ctx.runner, "resume") + spy.on(NeedsInstallSystemPackage, "install") + + ctx:execute(function() + ctx:require(NeedsInstallSystemPackage) + end) + + assert.spy(NeedsInstallSystemPackage.install).was_called(1) + assert.spy(ctx.runner.suspend).was_called(1) + assert.spy(ctx.runner.resume).was_called(1) + end) + + it("should not install required system package if needs_install is false", function() + local ctx = test_helpers.create_context() + + spy.on(ctx.runner, "suspend") + spy.on(ctx.runner, "resume") + spy.on(InstalledSystemPackage, "install") + + ctx:execute(function() + ctx:require(InstalledSystemPackage) + end) + + assert.spy(InstalledSystemPackage.install).was_called(0) + assert.spy(ctx.runner.suspend).was_called(0) + assert.spy(ctx.runner.resume).was_called(0) + end) + + it("should abort installation if system package installation fails", function() + local ctx = test_helpers.create_context() + local guard = spy.new() + + spy.on(ctx.runner, "suspend") + spy.on(ctx.runner, "resume") + spy.on(FailingSystemPackage, "install") + + local result = ctx:execute(function() + ctx:require(FailingSystemPackage) + guard() + end) + + assert.spy(FailingSystemPackage.install).was_called(1) + assert.spy(ctx.runner.suspend).was_called(1) + assert.spy(ctx.runner.resume).was_called(0) + assert.spy(guard).was_called(0) + assert.same(result, Result.failure "There was an issue.") + end) + + it("should continue installation if system package fails to install but --force is enabled", function() + local ctx = test_helpers.create_context { install_opts = { force = true } } + + spy.on(ctx.runner, "suspend") + spy.on(ctx.runner, "resume") + spy.on(FailingSystemPackage, "install") + + local result = ctx:execute(function() + ctx:require(FailingSystemPackage) + return Result.success "We forced this." + end) + + assert.spy(FailingSystemPackage.install).was_called(1) + assert.spy(ctx.runner.suspend).was_called(1) + assert.same(result, Result.success "We forced this.") + end) + end) end) diff --git a/tests/mason-core/installer/managers/cargo_spec.lua b/tests/mason-core/installer/managers/cargo_spec.lua index 66f89ca2..58969855 100644 --- a/tests/mason-core/installer/managers/cargo_spec.lua +++ b/tests/mason-core/installer/managers/cargo_spec.lua @@ -20,6 +20,7 @@ describe("cargo manager", function() vim.NIL, -- features vim.NIL, -- locked "my-crate", + firewall = true, } end) @@ -53,6 +54,7 @@ describe("cargo manager", function() vim.NIL, -- features "--locked", -- locked "my-crate", + firewall = true, } end) @@ -73,6 +75,7 @@ describe("cargo manager", function() { "--features", "lsp,cli" }, -- features vim.NIL, -- locked "my-crate", + firewall = true, } end) @@ -95,6 +98,7 @@ describe("cargo manager", function() vim.NIL, -- features vim.NIL, -- locked "my-crate", + firewall = true, } end) @@ -118,6 +122,7 @@ describe("cargo manager", function() vim.NIL, -- features vim.NIL, -- locked "my-crate", + firewall = true, } end) end) diff --git a/tests/mason-core/installer/managers/npm_spec.lua b/tests/mason-core/installer/managers/npm_spec.lua index 71dca637..e6d5a813 100644 --- a/tests/mason-core/installer/managers/npm_spec.lua +++ b/tests/mason-core/installer/managers/npm_spec.lua @@ -72,6 +72,7 @@ describe("npm manager", function() "my-package@1.0.0", vim.NIL, -- extra_packages vim.NIL, -- install_extra_args + firewall = true, } end) @@ -89,6 +90,7 @@ describe("npm manager", function() "my-package@1.0.0", { "extra-package" }, vim.NIL, -- install_extra_args + firewall = true, } end) @@ -107,6 +109,7 @@ describe("npm manager", function() "my-package@1.0.0", vim.NIL, -- extra_packages { "--registry", "https://registry.npmjs.org/" }, -- install_extra_args + firewall = true, } end) diff --git a/tests/mason-core/installer/managers/pypi_spec.lua b/tests/mason-core/installer/managers/pypi_spec.lua index 2fdf0002..34a9f8e3 100644 --- a/tests/mason-core/installer/managers/pypi_spec.lua +++ b/tests/mason-core/installer/managers/pypi_spec.lua @@ -83,6 +83,7 @@ describe("pypi manager", function() "--ignore-installed", { "--proxy", "http://localhost" }, { "pip" }, + firewall = true, } end) @@ -282,6 +283,7 @@ describe("pypi manager", function() "pypi-package==1.0.0", vim.NIL, -- extra_packages }, + firewall = true, } end) @@ -324,6 +326,7 @@ describe("pypi manager", function() "pypi-package[lsp]==1.0.0", vim.NIL, -- extra_packages }, + firewall = true, } end) @@ -351,6 +354,7 @@ describe("pypi manager", function() "pypi-package==1.0.0", { "extra-package" }, }, + firewall = true, } end) end) |
