diff --git a/lua/mason-core/fetch.lua b/lua/mason-core/fetch.lua index c8a8591cb..26ddb0f70 100644 --- a/lua/mason-core/fetch.lua +++ b/lua/mason-core/fetch.lua @@ -4,7 +4,7 @@ local a = require "mason-core.async" local async_uv = require "mason-core.async.uv" local log = require "mason-core.log" local platform = require "mason-core.platform" -local powershell = require "mason-core.managers.powershell" +local powershell = require "mason-core.installer.managers.powershell" local spawn = require "mason-core.spawn" local version = require "mason.version" diff --git a/lua/mason-core/installer/context.lua b/lua/mason-core/installer/context.lua index 7637209f9..21c9c26f0 100644 --- a/lua/mason-core/installer/context.lua +++ b/lua/mason-core/installer/context.lua @@ -303,10 +303,10 @@ end ---@param new_executable_rel_path string Relative path to the executable file to create. ---@param module string The python module to call. function InstallContext:write_pyvenv_exec_wrapper(new_executable_rel_path, module) - local pip3 = require "mason-core.managers.pip3" + local pypi = require "mason-core.installer.managers.pypi" local module_exists, module_err = pcall(function() local result = - self.spawn.python { "-c", ("import %s"):format(module), with_paths = { pip3.venv_path(self.cwd:get()) } } + self.spawn.python { "-c", ("import %s"):format(module), with_paths = { pypi.venv_path(self.cwd:get()) } } if not self.spawn.strict_mode then result:get_or_throw() end @@ -319,7 +319,7 @@ function InstallContext:write_pyvenv_exec_wrapper(new_executable_rel_path, modul new_executable_rel_path, ("%q -m %s"):format( path.concat { - pip3.venv_path(self.package:get_install_path()), + pypi.venv_path(self.package:get_install_path()), "python", }, module @@ -367,14 +367,13 @@ function InstallContext:write_shell_exec_wrapper(new_executable_rel_path, comman end return platform.when { unix = function() - local std = require "mason-core.managers.std" local formatted_envs = _.map(function(pair) local var, value = pair[1], pair[2] return ("export %s=%q"):format(var, value) end, _.to_pairs(env or {})) self.fs:write_file(new_executable_rel_path, BASH_TEMPLATE:format(_.join("\n", formatted_envs), command)) - std.chmod("+x", { new_executable_rel_path }) + self.fs:chmod_exec(new_executable_rel_path) return new_executable_rel_path end, win = function() diff --git a/lua/mason-core/installer/managers/common.lua b/lua/mason-core/installer/managers/common.lua index c730a3aa9..c13d3bff6 100644 --- a/lua/mason-core/installer/managers/common.lua +++ b/lua/mason-core/installer/managers/common.lua @@ -5,7 +5,7 @@ local async_uv = require "mason-core.async.uv" local installer = require "mason-core.installer" local log = require "mason-core.log" local platform = require "mason-core.platform" -local powershell = require "mason-core.managers.powershell" +local powershell = require "mason-core.installer.managers.powershell" local std = require "mason-core.installer.managers.std" local M = {} diff --git a/lua/mason-core/managers/powershell/init.lua b/lua/mason-core/installer/managers/powershell.lua similarity index 100% rename from lua/mason-core/managers/powershell/init.lua rename to lua/mason-core/installer/managers/powershell.lua diff --git a/lua/mason-core/installer/managers/pypi.lua b/lua/mason-core/installer/managers/pypi.lua index d596028e5..9f14657ed 100644 --- a/lua/mason-core/installer/managers/pypi.lua +++ b/lua/mason-core/installer/managers/pypi.lua @@ -15,6 +15,14 @@ local VENV_DIR = "venv" local is_executable = _.compose(_.equals(1), vim.fn.executable) +function M.venv_path(dir) + return path.concat { + dir, + VENV_DIR, + platform.is.win and "Scripts" or "bin", + } +end + ---@async ---@param candidates string[] local function resolve_python3(candidates) diff --git a/lua/mason-core/installer/managers/std.lua b/lua/mason-core/installer/managers/std.lua index 6e1a0d9ef..b4eb11aba 100644 --- a/lua/mason-core/installer/managers/std.lua +++ b/lua/mason-core/installer/managers/std.lua @@ -6,7 +6,7 @@ local installer = require "mason-core.installer" local log = require "mason-core.log" local path = require "mason-core.path" local platform = require "mason-core.platform" -local powershell = require "mason-core.managers.powershell" +local powershell = require "mason-core.installer.managers.powershell" local M = {} diff --git a/lua/mason-core/managers/cargo/client.lua b/lua/mason-core/managers/cargo/client.lua deleted file mode 100644 index 82dc85aa3..000000000 --- a/lua/mason-core/managers/cargo/client.lua +++ /dev/null @@ -1,18 +0,0 @@ -local fetch = require "mason-core.fetch" - -local M = {} - ----@alias CrateResponse {crate: {id: string, max_stable_version: string, max_version: string, newest_version: string}} - ----@async ----@param crate string ----@return Result # Result -function M.fetch_crate(crate) - return fetch(("https://crates.io/api/v1/crates/%s"):format(crate), { - headers = { - Accept = "application/json", - }, - }):map_catching(vim.json.decode) -end - -return M diff --git a/lua/mason-core/managers/cargo/init.lua b/lua/mason-core/managers/cargo/init.lua deleted file mode 100644 index 49a5841a3..000000000 --- a/lua/mason-core/managers/cargo/init.lua +++ /dev/null @@ -1,199 +0,0 @@ -local Optional = require "mason-core.optional" -local _ = require "mason-core.functional" -local a = require "mason-core.async" -local client = require "mason-core.managers.cargo.client" -local github = require "mason-core.managers.github" -local github_client = require "mason-core.managers.github.client" -local installer = require "mason-core.installer" -local path = require "mason-core.path" -local platform = require "mason-core.platform" -local spawn = require "mason-core.spawn" - -local get_bin_path = _.compose(path.concat, function(executable) - return _.append(executable, { "bin" }) -end, _.if_else(_.always(platform.is.win), _.format "%s.exe", _.identity)) - ----@param crate string -local function with_receipt(crate) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.cargo(crate)) - end -end - -local M = {} - ----@async ----@param crate string The crate to install. ----@param opts { git: { url: string, tag: boolean? }, features: string?, bin: string[]? }? -function M.crate(crate, opts) - return function() - if opts and opts.git and opts.git.tag then - local ctx = installer.context() - local repo = assert(opts.git.url:match "^https://github%.com/(.+)$", "git url needs to be github.com") - local source = github.tag { repo = repo } - source.with_receipt() - ctx.requested_version = Optional.of(source.tag) - M.install(crate, opts) - else - M.install(crate, opts).with_receipt() - end - end -end - ----@async ----@param crate string The crate to install. ----@param opts { git: { url: string, tag: boolean? }, features: string?, bin: string[]? }? -function M.install(crate, opts) - local ctx = installer.context() - opts = opts or {} - - local version - - if opts.git then - if opts.git.tag then - assert(ctx.requested_version:is_present(), "version is required when installing tagged git crate.") - end - version = ctx.requested_version - :map(function(version) - if opts.git.tag then - return { "--tag", version } - else - return { "--rev", version } - end - end) - :or_else(vim.NIL) - else - version = ctx.requested_version - :map(function(version) - return { "--version", version } - end) - :or_else(vim.NIL) - end - - ctx.spawn.cargo { - "install", - "--root", - ".", - "--locked", - version, - opts.git and { "--git", opts.git.url } or vim.NIL, - opts.features and { "--features", opts.features } or vim.NIL, - crate, - } - - if opts.bin then - _.each(function(bin) - ctx:link_bin(bin, get_bin_path(bin)) - end, opts.bin) - end - - return { - with_receipt = with_receipt(crate), - } -end - ----@alias InstalledCrate { name: string, version: string, github_ref: { owner: string, repo: string, ref: string }? } - ----@param line string ----@return InstalledCrate? crate -local function parse_installed_crate(line) - local name, version, context = line:match "^(.+)%s+v([^%s:]+) ?(.*):$" - if context then - local owner, repo, ref = context:match "^%(https://github%.com/(.+)/([^?]+).*#(.+)%)$" - if ref then - return { name = name, version = ref, github_ref = { owner = owner, repo = repo, ref = ref } } - end - end - if name and version then - return { name = name, version = version } - end -end - ----@param output string The `cargo install --list` output. ----@return table # Key is the crate name, value is its version. -function M.parse_installed_crates(output) - local installed_crates = {} - for _, line in ipairs(vim.split(output, "\n")) do - local installed_crate = parse_installed_crate(line) - if installed_crate then - installed_crates[installed_crate.name] = installed_crate - end - end - return installed_crates -end - ----@async ----@param install_dir string ----@return Result # Result> -local function get_installed_crates(install_dir) - return spawn - .cargo({ - "install", - "--list", - "--root", - ".", - cwd = install_dir, - }) - :map_catching(function(result) - return M.parse_installed_crates(result.stdout) - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - a.scheduler() - local crate_name = vim.fn.fnamemodify(receipt.primary_source.package, ":t") - return get_installed_crates(install_dir) - :ok() - :map(_.prop(crate_name)) - :map( - ---@param installed_crate InstalledCrate - function(installed_crate) - if installed_crate.github_ref then - ---@type GitHubCommit - local latest_commit = github_client - .fetch_commits( - ("%s/%s"):format(installed_crate.github_ref.owner, installed_crate.github_ref.repo), - { page = 1, per_page = 1 } - ) - :get_or_throw("Failed to fetch latest commits.")[1] - if not vim.startswith(latest_commit.sha, installed_crate.github_ref.ref) then - return { - name = receipt.primary_source.package, - current_version = installed_crate.github_ref.ref, - latest_version = latest_commit.sha, - } - end - else - ---@type CrateResponse - local crate_response = client.fetch_crate(crate_name):get_or_throw() - if installed_crate.version ~= crate_response.crate.max_stable_version then - return { - name = receipt.primary_source.package, - current_version = installed_crate.version, - latest_version = crate_response.crate.max_stable_version, - } - end - end - end - ) - :ok_or(_.always "Primary package is not outdated.") -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - a.scheduler() - local crate_name = vim.fn.fnamemodify(receipt.primary_source.package, ":t") - return get_installed_crates(install_dir) - :ok() - :map(_.prop(crate_name)) - :map(_.prop "version") - :ok_or(_.always "Failed to find cargo package version.") -end - -return M diff --git a/lua/mason-core/managers/composer/init.lua b/lua/mason-core/managers/composer/init.lua deleted file mode 100644 index 274e2bcb2..000000000 --- a/lua/mason-core/managers/composer/init.lua +++ /dev/null @@ -1,127 +0,0 @@ -local Optional = require "mason-core.optional" -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local installer = require "mason-core.installer" -local path = require "mason-core.path" -local platform = require "mason-core.platform" -local spawn = require "mason-core.spawn" - -local M = {} - -local create_bin_path = _.compose(path.concat, function(executable) - return _.append(executable, { "vendor", "bin" }) -end, _.if_else(_.always(platform.is.win), _.format "%s.bat", _.identity)) - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - - ctx.receipt:with_primary_source(ctx.receipt.composer(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.composer(packages[i])) - end - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The composer packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.packages(packages) - return function() - return M.require(packages).with_receipt() - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The composer packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.require(packages) - local ctx = installer.context() - local pkgs = _.list_copy(packages) - - if not ctx.fs:file_exists "composer.json" then - ctx.spawn.composer { "init", "--no-interaction", "--stability=stable" } - end - - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s:%s"):format(pkgs[1], version) - end) - - ctx.spawn.composer { "require", pkgs } - - if packages.bin then - _.each(function(executable) - ctx:link_bin(executable, create_bin_path(executable)) - end, packages.bin) - end - - return { - with_receipt = with_receipt(packages), - } -end - ----@async -function M.install() - local ctx = installer.context() - ctx.spawn.composer { - "install", - "--no-interaction", - "--no-dev", - "--optimize-autoloader", - "--classmap-authoritative", - } -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "composer" then - return Result.failure "Receipt does not have a primary source of type composer" - end - return spawn - .composer({ - "outdated", - "--no-interaction", - "--format=json", - cwd = install_dir, - }) - :map_catching(function(result) - local outdated_packages = vim.json.decode(result.stdout) - local outdated_package = _.find_first(function(pkg) - return pkg.name == receipt.primary_source.package - end, outdated_packages.installed) - return Optional.of_nilable(outdated_package) - :map(function(pkg) - if pkg.version ~= pkg.latest then - return { - name = pkg.name, - current_version = pkg.version, - latest_version = pkg.latest, - } - end - end) - :or_else_throw "Primary package is not outdated." - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - if receipt.primary_source.type ~= "composer" then - return Result.failure "Receipt does not have a primary source of type composer" - end - return spawn - .composer({ - "info", - "--format=json", - receipt.primary_source.package, - cwd = install_dir, - }) - :map_catching(function(result) - local info = vim.json.decode(result.stdout) - return info.versions[1] - end) -end - -return M diff --git a/lua/mason-core/managers/dotnet/init.lua b/lua/mason-core/managers/dotnet/init.lua deleted file mode 100644 index 984b24633..000000000 --- a/lua/mason-core/managers/dotnet/init.lua +++ /dev/null @@ -1,56 +0,0 @@ -local _ = require "mason-core.functional" -local installer = require "mason-core.installer" -local platform = require "mason-core.platform" - -local M = {} - -local create_bin_path = _.if_else(_.always(platform.is.win), _.format "%s.exe", _.identity) - ----@param package string -local function with_receipt(package) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.dotnet(package)) - end -end - ----@async ----@param pkg string ----@param opt { bin: string[]? }? -function M.package(pkg, opt) - return function() - return M.install(pkg, opt).with_receipt() - end -end - ----@async ----@param pkg string ----@param opt { bin: string[]? }? -function M.install(pkg, opt) - local ctx = installer.context() - ctx.spawn.dotnet { - "tool", - "update", - "--ignore-failed-sources", - "--tool-path", - ".", - ctx.requested_version - :map(function(version) - return { "--version", version } - end) - :or_else(vim.NIL), - pkg, - } - - if opt and opt.bin then - _.each(function(executable) - ctx:link_bin(executable, create_bin_path(executable)) - end, opt.bin) - end - - return { - with_receipt = with_receipt(pkg), - } -end - -return M diff --git a/lua/mason-core/managers/gem/init.lua b/lua/mason-core/managers/gem/init.lua deleted file mode 100644 index dc7448bf7..000000000 --- a/lua/mason-core/managers/gem/init.lua +++ /dev/null @@ -1,162 +0,0 @@ -local Optional = require "mason-core.optional" -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local installer = require "mason-core.installer" -local path = require "mason-core.path" -local platform = require "mason-core.platform" -local process = require "mason-core.process" -local providers = require "mason-core.providers" -local spawn = require "mason-core.spawn" - -local M = {} - ----@param install_dir string -local function env(install_dir) - return { - GEM_HOME = install_dir, - GEM_PATH = install_dir, - PATH = process.extend_path { path.concat { install_dir, "bin" } }, - } -end - -local create_bin_path = _.compose(path.concat, function(executable) - return _.append(executable, { "bin" }) -end, _.if_else(_.always(platform.is.win), _.format "%s.bat", _.identity)) - ----@async ----@param executable string -local function link_executable(executable) - local ctx = installer.context() - local bin_path = create_bin_path(executable) - if not ctx.fs:file_exists(bin_path) then - error(("Cannot link Gem executable %q because it doesn't exist in %q."):format(executable, bin_path), 0) - end - ctx:link_bin( - executable, - ctx:write_shell_exec_wrapper(executable, path.concat { ctx.package:get_install_path(), bin_path }, { - GEM_PATH = platform.when { - unix = function() - return ("%s:$GEM_PATH"):format(ctx.package:get_install_path()) - end, - win = function() - return ("%s;%%GEM_PATH%%"):format(ctx.package:get_install_path()) - end, - }, - }) - ) -end - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.gem(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.gem(packages[i])) - end - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The Gem packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.packages(packages) - return function() - return M.install(packages).with_receipt() - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The Gem packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.install(packages) - local ctx = installer.context() - local pkgs = _.list_copy(packages or {}) - - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s:%s"):format(pkgs[1], version) - end) - - ctx.spawn.gem { - "install", - "--no-user-install", - "--no-format-executable", - "--install-dir=.", - "--bindir=bin", - "--no-document", - pkgs, - env = { - GEM_HOME = ctx.cwd:get(), - }, - } - - if packages.bin then - _.each(link_executable, packages.bin) - end - - return { - with_receipt = with_receipt(packages), - } -end - ----@alias GemOutdatedPackage {name:string, current_version: string, latest_version: string} - ----Parses the stdout of the `gem list` command into a table ----@param output string -function M.parse_gem_list_output(output) - ---@type table - local gem_versions = {} - for _, line in ipairs(vim.split(output, "\n")) do - local gem_package, version = line:match "^(%S+) %((%S+)%)$" - if gem_package and version then - gem_versions[gem_package] = version - end - end - return gem_versions -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "gem" then - return Result.failure "Receipt does not have a primary source of type gem" - end - return M.get_installed_primary_package_version(receipt, install_dir) - :and_then(function(installed_version) - return providers.rubygems.get_latest_version(receipt.primary_source.package):map(function(latest) - return { - installed = installed_version, - latest = latest.version, - } - end) - end) - :and_then(function(versions) - if versions.installed ~= versions.latest then - return Result.success { - name = receipt.primary_source.package, - current_version = versions.installed, - latest_version = versions.latest, - } - else - return Result.failure "Primary package is not outdated." - end - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - return spawn - .gem({ - "list", - cwd = install_dir, - env = env(install_dir), - }) - :map_catching(function(result) - local gems = M.parse_gem_list_output(result.stdout) - return Optional.of_nilable(gems[receipt.primary_source.package]) - :or_else_throw "Failed to find gem package version." - end) -end - -return M diff --git a/lua/mason-core/managers/git/init.lua b/lua/mason-core/managers/git/init.lua deleted file mode 100644 index 099ea6c29..000000000 --- a/lua/mason-core/managers/git/init.lua +++ /dev/null @@ -1,76 +0,0 @@ -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local installer = require "mason-core.installer" -local spawn = require "mason-core.spawn" - -local M = {} - ----@param repo string -local function with_receipt(repo) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.git_remote(repo)) - end -end - ----@async ----@param opts {[1]: string, recursive: boolean, version: Optional?} The first item in the table is the repository to clone. -function M.clone(opts) - local ctx = installer.context() - local repo = assert(opts[1], "No git URL provided.") - ctx.spawn.git { - "clone", - "--depth", - "1", - opts.recursive and "--recursive" or vim.NIL, - repo, - ".", - } - _.coalesce(opts.version, ctx.requested_version):if_present(function(version) - ctx.spawn.git { "fetch", "--depth", "1", "origin", version } - ctx.spawn.git { "checkout", "FETCH_HEAD" } - end) - - return { - with_receipt = with_receipt(repo), - } -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_git_clone(receipt, install_dir) - if receipt.primary_source.type ~= "git" then - return Result.failure "Receipt does not have a primary source of type git" - end - return spawn.git({ "fetch", "origin", "HEAD", cwd = install_dir }):map_catching(function() - local result = spawn.git({ "rev-parse", "FETCH_HEAD", "HEAD", cwd = install_dir }):get_or_throw() - local remote_head, local_head = unpack(vim.split(result.stdout, "\n")) - if remote_head == local_head then - error("Git clone is up to date.", 2) - end - return { - name = receipt.primary_source.remote, - current_version = assert(local_head, "no local HEAD"), - latest_version = assert(remote_head, "no remote HEAD"), - } - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_revision(receipt, install_dir) - return spawn - .git({ - "rev-parse", - "--short", - "HEAD", - cwd = install_dir, - }) - :map_catching(function(result) - return assert(vim.trim(result.stdout)) - end) -end - -return M diff --git a/lua/mason-core/managers/github/client.lua b/lua/mason-core/managers/github/client.lua deleted file mode 100644 index e8301f721..000000000 --- a/lua/mason-core/managers/github/client.lua +++ /dev/null @@ -1,84 +0,0 @@ -local _ = require "mason-core.functional" -local fetch = require "mason-core.fetch" -local spawn = require "mason-core.spawn" - -local M = {} - ----@alias GitHubCommit { sha: string } ----@alias GitHubRef { ref: string } - -local stringify_params = _.compose(_.join "&", _.map(_.join "="), _.sort_by(_.head), _.to_pairs) - ----@param path string ----@param opts { params: table? }? ----@return Result # JSON decoded response. -local function gh_api_call(path, opts) - if opts and opts.params then - local params = stringify_params(opts.params) - path = ("%s?%s"):format(path, params) - end - return spawn - .gh({ "api", path, env = { CLICOLOR_FORCE = 0 } }) - :map(_.prop "stdout") - :or_else(function() - return fetch(("https://api.github.com/%s"):format(path), { - headers = { - Accept = "application/vnd.github.v3+json; q=1.0, application/json; q=0.8", - }, - }) - end) - :map_catching(vim.json.decode) -end - -M.api_call = gh_api_call - ----@async ----@param repo string The GitHub repo ("username/repo"). ----@return Result # Result -function M.fetch_latest_release(repo) - local path = ("repos/%s/releases/latest"):format(repo) - return gh_api_call(path) -end - ----@async ----@param repo string The GitHub repo ("username/repo"). ----@return Result # Result -function M.fetch_all_releases(repo) - local path = ("repos/%s/releases"):format(repo) - return gh_api_call(path) -end - ----@async ----@param repo string The GitHub repo ("username/repo"). ----@return Result # Result -function M.fetch_all_tags(repo) - local path = ("repos/%s/git/matching-refs/tags"):format(repo) - return gh_api_call(path) -end - ----@async ----@param repo string The GitHub repo ("username/repo"). ----@param opts { page: integer?, per_page: integer? }? ----@return Result # Result -function M.fetch_commits(repo, opts) - local path = ("repos/%s/commits"):format(repo) - return gh_api_call(path, { - params = { - page = opts and opts.page or 1, - per_page = opts and opts.per_page or 30, - }, - }):map_err(function() - return ("Failed to fetch commits for GitHub repository %s."):format(repo) - end) -end - ----@alias GitHubRateLimit {limit: integer, remaining: integer, reset: integer, used: integer} ----@alias GitHubRateLimitResponse {resources: { core: GitHubRateLimit }} - ----@async ----@return Result # Result -function M.fetch_rate_limit() - return gh_api_call "rate_limit" -end - -return M diff --git a/lua/mason-core/managers/github/init.lua b/lua/mason-core/managers/github/init.lua deleted file mode 100644 index 2d5937900..000000000 --- a/lua/mason-core/managers/github/init.lua +++ /dev/null @@ -1,208 +0,0 @@ -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local installer = require "mason-core.installer" -local platform = require "mason-core.platform" -local providers = require "mason-core.providers" -local settings = require "mason.settings" -local std = require "mason-core.managers.std" - -local M = {} - ----@class InstallReceiptGitHubReleaseFileSource ----@field type '"github_release_file"' ----@field repo string ----@field file string ----@field release string - ----@param repo string ----@param asset_file string ----@param release string -local function with_release_file_receipt(repo, asset_file, release) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source { - type = "github_release_file", - repo = repo, - file = asset_file, - release = release, - } - end -end - ----@class InstallReceiptGitHubTagSource ----@field type '"github_tag"' ----@field repo string ----@field tag string - ----@param repo string ----@param tag string -local function with_tag_receipt(repo, tag) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source { - type = "github_tag", - repo = repo, - tag = tag, - } - end -end - ----@async ----@param opts {repo: string, version: Optional?} -function M.release_version(opts) - local ctx = installer.context() - ---@type string - local release = _.coalesce(opts.version, ctx.requested_version):or_else_get(function() - return providers.github - .get_latest_release(opts.repo) - :map(_.prop "tag_name") - :get_or_throw "Failed to fetch latest release from GitHub API. Refer to :h mason-provider-errors for more information." - end) - - return { - with_receipt = function() - ctx.receipt:with_primary_source { - type = "github_release", - repo = opts.repo, - release = release, - } - end, - release = release, - } -end - ----@async ----@param opts {repo: string, version: Optional?, asset_file: string|fun(release: string):string} -function M.release_file(opts) - local source = M.release_version(opts) - ---@type string - local asset_file - if type(opts.asset_file) == "function" then - asset_file = opts.asset_file(source.release) - elseif type(opts.asset_file) == "string" then - asset_file = opts.asset_file --[[@as string]] - end - if not asset_file then - error( - ( - "Could not find which release file to download.\n" - .. "Most likely the current operating system or architecture is not supported (%s_%s)." - ):format(platform.sysname, platform.arch), - 0 - ) - end - local download_url = settings.current.github.download_url_template:format(opts.repo, source.release, asset_file) - return { - release = source.release, - download_url = download_url, - asset_file = asset_file, - with_receipt = with_release_file_receipt(opts.repo, download_url, source.release), - } -end - ----@async ----@param opts {repo: string, version: Optional?} -function M.tag(opts) - local ctx = installer.context() - local tag = _.coalesce(opts.version, ctx.requested_version):or_else_get(function() - return providers.github - .get_latest_tag(opts.repo) - :map(_.prop "tag") - :get_or_throw "Failed to fetch latest tag from GitHub API." - end) - - return { - tag = tag, - with_receipt = with_tag_receipt(opts.repo, tag), - } -end - ----@param filename string ----@param processor async fun(opts: table) -local function release_file_processor(filename, processor) - ---@async - ---@param opts {repo: string, version: Optional|nil, asset_file: string|fun(release: string):string} - return function(opts) - local release_file_source = M.release_file(opts) - std.download_file(release_file_source.download_url, filename) - processor(opts) - return release_file_source - end -end - -M.unzip_release_file = release_file_processor("archive.zip", function() - std.unzip("archive.zip", ".") -end) - -M.untarzst_release_file = release_file_processor("archive.tar.zst", function(opts) - std.untarzst("archive.tar.zst", { strip_components = opts.strip_components }) -end) - -M.untarxz_release_file = release_file_processor("archive.tar.xz", function(opts) - std.untarxz("archive.tar.xz", { strip_components = opts.strip_components }) -end) - -M.untargz_release_file = release_file_processor("archive.tar.gz", function(opts) - std.untar("archive.tar.gz", { strip_components = opts.strip_components }) -end) - ----@async ----@param opts {repo: string, out_file:string, asset_file: string|fun(release: string):string} -function M.download_release_file(opts) - local release_file_source = M.release_file(opts) - std.download_file(release_file_source.download_url, assert(opts.out_file, "out_file is required")) - return release_file_source -end - ----@async ----@param opts {repo: string, out_file:string, asset_file: string|fun(release: string):string} -function M.gunzip_release_file(opts) - local release_file_source = M.release_file(opts) - local gzipped_file = ("%s.gz"):format(assert(opts.out_file, "out_file must be specified")) - std.download_file(release_file_source.download_url, gzipped_file) - std.gunzip(gzipped_file) - return release_file_source -end - ----@async ----@param receipt InstallReceipt -function M.check_outdated_primary_package_release(receipt) - local source = receipt.primary_source - if source.type ~= "github_release" and source.type ~= "github_release_file" then - return Result.failure "Receipt does not have a primary source of type (github_release|github_release_file)." - end - return providers.github.get_latest_release(source.repo):map_catching( - ---@param latest_release GitHubRelease - function(latest_release) - if source.release ~= latest_release.tag_name then - return { - name = source.repo, - current_version = source.release, - latest_version = latest_release.tag_name, - } - end - error "Primary package is not outdated." - end - ) -end - ----@async ----@param receipt InstallReceipt -function M.check_outdated_primary_package_tag(receipt) - local source = receipt.primary_source - if source.type ~= "github_tag" then - return Result.failure "Receipt does not have a primary source of type github_tag." - end - return providers.github.get_latest_tag(source.repo):map(_.prop "tag"):map_catching(function(latest_tag) - if source.tag ~= latest_tag then - return { - name = source.repo, - current_version = source.tag, - latest_version = latest_tag, - } - end - error "Primary package is not outdated." - end) -end - -return M diff --git a/lua/mason-core/managers/go/init.lua b/lua/mason-core/managers/go/init.lua deleted file mode 100644 index 0452400d5..000000000 --- a/lua/mason-core/managers/go/init.lua +++ /dev/null @@ -1,167 +0,0 @@ -local Optional = require "mason-core.optional" -local _ = require "mason-core.functional" -local a = require "mason-core.async" -local installer = require "mason-core.installer" -local platform = require "mason-core.platform" -local spawn = require "mason-core.spawn" - -local M = {} - -local create_bin_path = _.if_else(_.always(platform.is.win), _.format "%s.exe", _.identity) - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.go(packages[1])) - -- Install secondary packages - for i = 2, #packages do - local pkg = packages[i] - ctx.receipt:with_secondary_source(ctx.receipt.go(pkg)) - end - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The go packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.packages(packages) - return function() - M.install(packages).with_receipt() - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The go packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.install(packages) - local ctx = installer.context() - local env = { - GOBIN = ctx.cwd:get(), - } - -- Install the head package - do - local head_package = packages[1] - local version = ctx.requested_version:or_else "latest" - ctx.spawn.go { - "install", - "-v", - ("%s@%s"):format(head_package, version), - env = env, - } - end - - -- Install secondary packages - for i = 2, #packages do - ctx.spawn.go { "install", "-v", ("%s@latest"):format(packages[i]), env = env } - end - - if packages.bin then - _.each(function(executable) - ctx:link_bin(executable, create_bin_path(executable)) - end, packages.bin) - end - - return { - with_receipt = with_receipt(packages), - } -end - ----@param output string The output from `go version -m` command. -function M.parse_mod_version_output(output) - ---@type {path: string[], mod: string[], dep: string[], build: string[]} - local result = {} - local lines = vim.split(output, "\n") - for _, line in ipairs { unpack(lines, 2) } do - local type, id, value = unpack(vim.split(line, "%s+", { trimempty = true })) - if type and id then - result[type] = result[type] or {} - result[type][id] = value or "" - end - end - return result -end - -local trim_wildcard_suffix = _.gsub("/%.%.%.$", "") - ----@param pkg string -function M.parse_package_mod(pkg) - if _.starts_with("github.com", pkg) then - local components = _.split("/", pkg) - return trim_wildcard_suffix(_.join("/", { - components[1], -- github.com - components[2], -- owner - components[3], -- repo - })) - elseif _.starts_with("golang.org", pkg) then - local components = _.split("/", pkg) - return trim_wildcard_suffix(_.join("/", { - components[1], -- golang.org - components[2], -- x - components[3], -- owner - components[4], -- repo - })) - else - -- selene: allow(if_same_then_else) - local components = _.split("/", pkg) - return trim_wildcard_suffix(_.join("/", { - components[1], - components[2], - components[3], - })) - end -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - a.scheduler() - local normalized_pkg_name = trim_wildcard_suffix(receipt.primary_source.package) - -- trims e.g. golang.org/x/tools/gopls to gopls - local executable = vim.fn.fnamemodify(normalized_pkg_name, ":t") - return spawn - .go({ - "version", - "-m", - platform.is.win and ("%s.exe"):format(executable) or executable, - cwd = install_dir, - }) - :map_catching(function(result) - local parsed_output = M.parse_mod_version_output(result.stdout) - return Optional.of_nilable(parsed_output.mod[M.parse_package_mod(receipt.primary_source.package)]) - :or_else_throw "Failed to parse mod version" - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - local normalized_pkg_name = M.parse_package_mod(receipt.primary_source.package) - return spawn - .go({ - "list", - "-json", - "-m", - ("%s@latest"):format(normalized_pkg_name), - cwd = install_dir, - }) - :map_catching(function(result) - ---@type {Path: string, Version: string} - local output = vim.json.decode(result.stdout) - return Optional.of_nilable(output.Version) - :map(function(latest_version) - local installed_version = M.get_installed_primary_package_version(receipt, install_dir) - :get_or_throw() - if installed_version ~= latest_version then - return { - name = normalized_pkg_name, - current_version = assert(installed_version, "missing installed_version"), - latest_version = assert(latest_version, "missing latest_version"), - } - end - end) - :or_else_throw "Primary package is not outdated." - end) -end - -return M diff --git a/lua/mason-core/managers/luarocks/init.lua b/lua/mason-core/managers/luarocks/init.lua deleted file mode 100644 index 321346f91..000000000 --- a/lua/mason-core/managers/luarocks/init.lua +++ /dev/null @@ -1,137 +0,0 @@ -local Optional = require "mason-core.optional" -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local installer = require "mason-core.installer" -local path = require "mason-core.path" -local platform = require "mason-core.platform" -local spawn = require "mason-core.spawn" - -local M = {} - -local create_bin_path = _.compose(path.concat, function(executable) - return _.append(executable, { "bin" }) -end, _.if_else(_.always(platform.is.win), _.format "%s.bat", _.identity)) - ----@param package string -local function with_receipt(package) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.luarocks(package)) - end -end - ----@param package string The luarock package to install. ----@param opts { dev: boolean?, server: string?, bin: string[]? }? -function M.package(package, opts) - return function() - return M.install(package, opts).with_receipt() - end -end - ----@async ----@param pkg string: The luarock package to install. ----@param opts { dev: boolean?, server: string?, bin: string[]? }? -function M.install(pkg, opts) - opts = opts or {} - local ctx = installer.context() - ctx:promote_cwd() - ctx.spawn.luarocks { - "install", - "--tree", - ctx.cwd:get(), - opts.dev and "--dev" or vim.NIL, - opts.server and ("--server=%s"):format(opts.server) or vim.NIL, - pkg, - ctx.requested_version:or_else(vim.NIL), - } - if opts.bin then - _.each(function(executable) - ctx:link_bin(executable, create_bin_path(executable)) - end, opts.bin) - end - return { - with_receipt = with_receipt(pkg), - } -end - ----@alias InstalledLuarock {package: string, version: string, arch: string, nrepo: string, namespace: string} - ----@type fun(output: string): InstalledLuarock[] -M.parse_installed_rocks = _.compose( - _.map(_.compose( - -- https://github.com/luarocks/luarocks/blob/fbd3566a312e647cde57b5d774533731e1aa844d/src/luarocks/search.lua#L317 - _.zip_table { "package", "version", "arch", "nrepo", "namespace" }, - _.split "\t" - )), - _.split "\n" -) - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - if receipt.primary_source.type ~= "luarocks" then - return Result.failure "Receipt does not have a primary source of type luarocks" - end - local primary_package = receipt.primary_source.package - return spawn - .luarocks({ - "list", - "--tree", - install_dir, - "--porcelain", - }) - :map_catching(function(result) - local luarocks = M.parse_installed_rocks(result.stdout) - return Optional.of_nilable(_.find_first(_.prop_eq("package", primary_package), luarocks)) - :map(_.prop "version") - :or_else_throw() - end) -end - ----@alias OutdatedLuarock {name: string, installed: string, available: string, repo: string} - ----@type fun(output: string): OutdatedLuarock[] -M.parse_outdated_rocks = _.compose( - _.map(_.compose( - -- https://github.com/luarocks/luarocks/blob/fbd3566a312e647cde57b5d774533731e1aa844d/src/luarocks/cmd/list.lua#L59 - _.zip_table { "name", "installed", "available", "repo" }, - _.split "\t" - )), - _.split "\n" -) - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "luarocks" then - return Result.failure "Receipt does not have a primary source of type luarocks" - end - local primary_package = receipt.primary_source.package - return spawn - .luarocks({ - "list", - "--outdated", - "--tree", - install_dir, - "--porcelain", - }) - :map_catching(function(result) - local outdated_rocks = M.parse_outdated_rocks(result.stdout) - return Optional.of_nilable(_.find_first(_.prop_eq("name", primary_package), outdated_rocks)) - :map( - ---@param outdated_rock OutdatedLuarock - function(outdated_rock) - return { - name = outdated_rock.name, - current_version = assert(outdated_rock.installed, "missing installed luarock version"), - latest_version = assert(outdated_rock.available, "missing available luarock version"), - } - end - ) - :or_else_throw() - end) -end - -return M diff --git a/lua/mason-core/managers/npm/init.lua b/lua/mason-core/managers/npm/init.lua deleted file mode 100644 index bcefb55dc..000000000 --- a/lua/mason-core/managers/npm/init.lua +++ /dev/null @@ -1,136 +0,0 @@ -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local installer = require "mason-core.installer" -local path = require "mason-core.path" -local platform = require "mason-core.platform" -local providers = require "mason-core.providers" -local spawn = require "mason-core.spawn" - -local list_copy = _.list_copy - -local M = {} - -local create_bin_path = _.compose(path.concat, function(executable) - return _.append(executable, { "node_modules", ".bin" }) -end, _.if_else(_.always(platform.is.win), _.format "%s.cmd", _.identity)) - ----@async ----@param ctx InstallContext -local function ensure_npm_root(ctx) - if not (ctx.fs:dir_exists "node_modules" or ctx.fs:file_exists "package.json") then - -- Create a package.json to set a boundary for where npm installs packages. - ctx.spawn.npm { "init", "--yes", "--scope=mason" } - ctx.stdio_sink.stdout "Initialized npm root\n" - end -end - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.npm(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.npm(packages[i])) - end - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The npm packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.packages(packages) - return function() - return M.install(packages).with_receipt() - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The npm packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.install(packages) - local ctx = installer.context() - local pkgs = list_copy(packages) - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s@%s"):format(pkgs[1], version) - end) - - -- Use global-style. The reasons for this are: - -- a) To avoid polluting the executables (aka bin-links) that npm creates. - -- b) The installation is, after all, more similar to a "global" installation. We don't really gain - -- any of the benefits of not using global style (e.g., deduping the dependency tree). - -- - -- We write to .npmrc manually instead of going through npm because managing a local .npmrc file - -- is a bit unreliable across npm versions (especially <7), so we take extra measures to avoid - -- inadvertently polluting global npm config. - ctx.fs:append_file(".npmrc", "global-style=true") - - ensure_npm_root(ctx) - ctx.spawn.npm { "install", pkgs } - - if packages.bin then - _.each(function(executable) - ctx:link_bin(executable, create_bin_path(executable)) - end, packages.bin) - end - - return { - with_receipt = with_receipt(packages), - } -end - ----@async ----@param exec_args string[] The arguments to pass to npm exec. -function M.exec(exec_args) - local ctx = installer.context() - ctx.spawn.npm { "exec", "--yes", "--", exec_args } -end - ----@async ----@param script string The npm script to run. -function M.run(script) - local ctx = installer.context() - ctx.spawn.npm { "run", script } -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - if receipt.primary_source.type ~= "npm" then - return Result.failure "Receipt does not have a primary source of type npm" - end - return spawn.npm({ "ls", "--json", cwd = install_dir }):map_catching(function(result) - local npm_packages = vim.json.decode(result.stdout) - return npm_packages.dependencies[receipt.primary_source.package].version - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "npm" then - return Result.failure "Receipt does not have a primary source of type npm" - end - local primary_package = receipt.primary_source.package - return M.get_installed_primary_package_version(receipt, install_dir) - :and_then(function(installed_version) - return providers.npm.get_latest_version(primary_package):map(function(response) - return { - installed = installed_version, - latest = response.version, - } - end) - end) - :and_then(function(versions) - if versions.installed ~= versions.latest then - return Result.success { - name = primary_package, - current_version = versions.installed, - latest_version = versions.latest, - } - else - return Result.failure "Primary package is not outdated." - end - end) -end - -return M diff --git a/lua/mason-core/managers/opam/init.lua b/lua/mason-core/managers/opam/init.lua deleted file mode 100644 index ae7425100..000000000 --- a/lua/mason-core/managers/opam/init.lua +++ /dev/null @@ -1,62 +0,0 @@ -local _ = require "mason-core.functional" -local installer = require "mason-core.installer" -local path = require "mason-core.path" -local platform = require "mason-core.platform" - -local M = {} - -local list_copy = _.list_copy - -local create_bin_path = _.compose(path.concat, function(executable) - return _.append(executable, { "bin" }) -end, _.if_else(_.always(platform.is.win), _.format "%s.exe", _.identity)) - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.opam(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.opam(packages[i])) - end - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The opam packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.packages(packages) - return function() - return M.install(packages).with_receipt() - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The opam packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.install(packages) - local ctx = installer.context() - local pkgs = list_copy(packages) - - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s.%s"):format(pkgs[1], version) - end) - - ctx.spawn.opam { - "install", - "--destdir=.", - "--yes", - "--verbose", - pkgs, - } - - if packages.bin then - _.each(function(executable) - ctx:link_bin(executable, create_bin_path(executable)) - end, packages.bin) - end - - return { - with_receipt = with_receipt(packages), - } -end - -return M diff --git a/lua/mason-core/managers/pip3/init.lua b/lua/mason-core/managers/pip3/init.lua deleted file mode 100644 index 813bc9ea0..000000000 --- a/lua/mason-core/managers/pip3/init.lua +++ /dev/null @@ -1,173 +0,0 @@ -local Optional = require "mason-core.optional" -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local a = require "mason-core.async" -local installer = require "mason-core.installer" -local path = require "mason-core.path" -local platform = require "mason-core.platform" -local providers = require "mason-core.providers" -local settings = require "mason.settings" -local spawn = require "mason-core.spawn" - -local VENV_DIR = "venv" - -local M = {} - -local create_bin_path = _.compose(path.concat, function(executable) - return _.append(executable, { VENV_DIR, platform.is.win and "Scripts" or "bin" }) -end, _.if_else(_.always(platform.is.win), _.format "%s.exe", _.identity)) - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.pip3(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.pip3(packages[i])) - end - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The pip packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.packages(packages) - return function() - return M.install(packages).with_receipt() - end -end - ----@async ----@param packages { [number]: string, bin: string[]? } The pip packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.install(packages) - local ctx = installer.context() - local pkgs = _.list_copy(packages) - - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s==%s"):format(pkgs[1], version) - end) - - a.scheduler() - - local executables = platform.is.win and { "python", "python3" } or { "python3", "python" } - - -- pip3 will hardcode the full path to venv executables, so we need to promote cwd to make sure pip uses the final destination path. - ctx:promote_cwd() - - -- Find first executable that manages to create venv - local executable = _.find_first(function(executable) - return pcall(ctx.spawn[executable], { "-m", "venv", VENV_DIR }) - end, executables) - - Optional.of_nilable(executable) - :if_present(function() - if settings.current.pip.upgrade_pip then - ctx.spawn.python { - "-m", - "pip", - "--disable-pip-version-check", - "install", - "-U", - settings.current.pip.install_args, - "pip", - with_paths = { M.venv_path(ctx.cwd:get()) }, - } - end - ctx.spawn.python { - "-m", - "pip", - "--disable-pip-version-check", - "install", - "-U", - settings.current.pip.install_args, - pkgs, - with_paths = { M.venv_path(ctx.cwd:get()) }, - } - end) - :or_else_throw "Unable to create python3 venv environment." - - if packages.bin then - _.each(function(bin) - ctx:link_bin(bin, create_bin_path(bin)) - end, packages.bin) - end - - return { - with_receipt = with_receipt(packages), - } -end - ----@param pkg string ----@return string -function M.normalize_package(pkg) - -- https://stackoverflow.com/a/60307740 - local s = pkg:gsub("%[.*%]", "") - return s -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "pip3" then - return Result.failure "Receipt does not have a primary source of type pip3" - end - local normalized_package = M.normalize_package(receipt.primary_source.package) - return M.get_installed_primary_package_version(receipt, install_dir):and_then(function(installed_version) - return providers.pypi - .get_latest_version(normalized_package) - :map(function(latest) - return { - current = installed_version, - latest = latest.version, - } - end) - :and_then(function(versions) - if versions.current ~= versions.latest then - return Result.success { - name = normalized_package, - current_version = versions.current, - latest_version = versions.latest, - } - else - return Result.failure "Primary package is not outdated." - end - end) - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - if receipt.primary_source.type ~= "pip3" then - return Result.failure "Receipt does not have a primary source of type pip3" - end - return spawn - .python({ - "-m", - "pip", - "list", - "--format=json", - cwd = install_dir, - with_paths = { M.venv_path(install_dir) }, - }) - :map_catching(function(result) - local pip_packages = vim.json.decode(result.stdout) - local normalized_pip_package = M.normalize_package(receipt.primary_source.package) - local pip_package = _.find_first(function(pkg) - return pkg.name == normalized_pip_package - end, pip_packages) - return Optional.of_nilable(pip_package) - :map(function(pkg) - return pkg.version - end) - :or_else_throw "Unable to find pip package." - end) -end - ----@param install_dir string -function M.venv_path(install_dir) - return path.concat { install_dir, VENV_DIR, platform.is.win and "Scripts" or "bin" } -end - -return M diff --git a/lua/mason-core/managers/std/init.lua b/lua/mason-core/managers/std/init.lua deleted file mode 100644 index b3116d7ab..000000000 --- a/lua/mason-core/managers/std/init.lua +++ /dev/null @@ -1,196 +0,0 @@ -local Result = require "mason-core.result" -local a = require "mason-core.async" -local fetch = require "mason-core.fetch" -local installer = require "mason-core.installer" -local path = require "mason-core.path" -local platform = require "mason-core.platform" -local powershell = require "mason-core.managers.powershell" - -local M = {} - -local function with_system_executable_receipt(executable) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.system(executable)) - end -end - ----@async ----@param executable string ----@param opts {help_url:string?}? -function M.ensure_executable(executable, opts) - local ctx = installer.context() - opts = opts or {} - a.scheduler() - if vim.fn.executable(executable) ~= 1 then - ctx.stdio_sink.stderr(("%s was not found in path.\n"):format(executable)) - if opts.help_url then - ctx.stdio_sink.stderr(("See %s for installation instructions.\n"):format(opts.help_url)) - end - error("Installation failed: system executable was not found.", 0) - end - - return { - with_receipt = with_system_executable_receipt(executable), - } -end - ----@async ----@param url string ----@param out_file string -function M.download_file(url, out_file) - local ctx = installer.context() - ctx.stdio_sink.stdout(("Downloading file %q…\n"):format(url)) - fetch(url, { - out_file = path.concat { ctx.cwd:get(), out_file }, - }) - :map_err(function(err) - return ("Failed to download file %q.\n%s"):format(url, err) - end) - :get_or_throw() -end - ----@async ----@param file string ----@param dest string -function M.unzip(file, dest) - local ctx = installer.context() - platform.when { - unix = function() - ctx.spawn.unzip { "-d", dest, file } - end, - win = function() - powershell.command( - ("Microsoft.PowerShell.Archive\\Expand-Archive -Path %q -DestinationPath %q"):format(file, dest), - {}, - ctx.spawn - ) - end, - } - pcall(function() - -- make sure the .zip archive doesn't linger - ctx.fs:unlink(file) - end) -end - ----@param file string -local function win_decompress(file) - local ctx = installer.context() - Result.run_catching(function() - ctx.spawn.gzip { "-d", file } - end) - :recover_catching(function() - ctx.spawn["7z"] { "x", "-y", "-r", file } - end) - :recover_catching(function() - ctx.spawn.peazip { "-ext2here", path.concat { ctx.cwd:get(), file } } -- peazip requires absolute paths - end) - :recover_catching(function() - ctx.spawn.wzunzip { file } - end) - :recover_catching(function() - ctx.spawn.winrar { "e", file } - end) - :get_or_throw(("Unable to unpack %s."):format(file)) -end - ----@async ----@param file string ----@param opts { strip_components?: integer }? -function M.untar(file, opts) - opts = opts or {} - local ctx = installer.context() - ctx.spawn.tar { - opts.strip_components and { "--strip-components", opts.strip_components } or vim.NIL, - "--no-same-owner", - "-xvf", - file, - } - pcall(function() - ctx.fs:unlink(file) - end) -end - ----@async ----@param file string ----@param opts { strip_components?: integer }? -function M.untarzst(file, opts) - opts = opts or {} - platform.when { - unix = function() - M.untar(file, opts) - end, - win = function() - local ctx = installer.context() - local uncompressed_tar = file:gsub("%.zst$", "") - ctx.spawn.zstd { "-dfo", uncompressed_tar, file } - M.untar(uncompressed_tar, opts) - end, - } -end - ----@async ----@param file string ----@param opts { strip_components?: integer }? -function M.untarxz(file, opts) - opts = opts or {} - local ctx = installer.context() - platform.when { - unix = function() - M.untar(file, opts) - end, - win = function() - Result.run_catching(function() - win_decompress(file) -- unpack .tar.xz to .tar - local uncompressed_tar = file:gsub("%.xz$", "") - M.untar(uncompressed_tar, opts) - end):recover(function() - ctx.spawn.arc { - "unarchive", - opts.strip_components and { "--strip-components", opts.strip_components } or vim.NIL, - file, - } - pcall(function() - ctx.fs:unlink(file) - end) - end) - end, - } -end - ----@async ----@param file string -function M.gunzip(file) - platform.when { - unix = function() - local ctx = installer.context() - ctx.spawn.gzip { "-d", file } - end, - win = function() - win_decompress(file) - end, - } -end - ----@async ----@param flags string The chmod flag to apply. ----@param files string[] A list of relative paths to apply the chmod on. -function M.chmod(flags, files) - if platform.is.unix then - local ctx = installer.context() - ctx.spawn.chmod { flags, files } - end -end - ----@async ----Wrapper around vim.ui.select. ----@param items table ----@params opts -function M.select(items, opts) - assert(not platform.is_headless, "Tried to prompt for user input while in headless mode.") - a.scheduler() - local async_select = a.promisify(vim.ui.select) - return async_select(items, opts) -end - -return M diff --git a/tests/mason-core/installer/context_spec.lua b/tests/mason-core/installer/context_spec.lua index af99089f9..646f7e309 100644 --- a/tests/mason-core/installer/context_spec.lua +++ b/tests/mason-core/installer/context_spec.lua @@ -1,8 +1,7 @@ local match = require "luassert.match" local path = require "mason-core.path" -local pip3 = require "mason-core.managers.pip3" +local pypi = require "mason-core.installer.managers.pypi" local registry = require "mason-registry" -local std = require "mason-core.managers.std" local stub = require "luassert.stub" describe("installer", function() @@ -21,9 +20,9 @@ describe("installer", function() stub(ctx.fs, "write_file") stub(ctx.fs, "file_exists") stub(ctx.fs, "dir_exists") + stub(ctx.fs, "chmod_exec") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), "my-executable").returns(false) ctx.fs.dir_exists.on_call_with(match.is_ref(ctx.fs), "my-executable").returns(false) - stub(std, "chmod") ctx:write_shell_exec_wrapper("my-executable", "bash -c 'echo $GREETING'", { GREETING = "Hello World!", @@ -52,7 +51,6 @@ exec bash -c 'echo $GREETING' "$@"]] stub(ctx.fs, "dir_exists") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), "my-executable").returns(false) ctx.fs.dir_exists.on_call_with(match.is_ref(ctx.fs), "my-executable").returns(false) - stub(std, "chmod") ctx:write_shell_exec_wrapper("my-executable", "cmd.exe /C echo %GREETING%", { GREETING = "Hello World!", @@ -156,7 +154,7 @@ cmd.exe /C echo %GREETING% %*]] assert.spy(ctx.write_shell_exec_wrapper).was_called_with( match.is_ref(ctx), "my-wrapper-script", - ("%q -m my-module"):format(path.concat { pip3.venv_path(dummy:get_install_path()), "python" }) + ("%q -m my-module"):format(path.concat { pypi.venv_path(dummy:get_install_path()), "python" }) ) end) diff --git a/tests/mason-core/managers/powershell_spec.lua b/tests/mason-core/installer/managers/powershell_spec.lua similarity index 95% rename from tests/mason-core/managers/powershell_spec.lua rename to tests/mason-core/installer/managers/powershell_spec.lua index 56ec243e5..86bbe1f9c 100644 --- a/tests/mason-core/managers/powershell_spec.lua +++ b/tests/mason-core/installer/managers/powershell_spec.lua @@ -6,8 +6,8 @@ local stub = require "luassert.stub" describe("powershell manager", function() local function powershell() - package.loaded["mason-core.managers.powershell"] = nil - return require "mason-core.managers.powershell" + package.loaded["mason-core.installer.managers.powershell"] = nil + return require "mason-core.installer.managers.powershell" end it("should use pwsh if available", function() diff --git a/tests/mason-core/managers/cargo_spec.lua b/tests/mason-core/managers/cargo_spec.lua deleted file mode 100644 index 92d045882..000000000 --- a/tests/mason-core/managers/cargo_spec.lua +++ /dev/null @@ -1,350 +0,0 @@ -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local cargo = require "mason-core.managers.cargo" -local cargo_client = require "mason-core.managers.cargo.client" -local github = require "mason-core.managers.github" -local github_client = require "mason-core.managers.github.client" -local installer = require "mason-core.installer" -local match = require "luassert.match" -local mock = require "luassert.mock" -local path = require "mason-core.path" -local spawn = require "mason-core.spawn" -local spy = require "luassert.spy" -local stub = require "luassert.stub" - -describe("cargo manager", function() - it( - "should call cargo install", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, cargo.crate "my-crate") - assert.spy(ctx.spawn.cargo).was_called(1) - assert.spy(ctx.spawn.cargo).was_called_with { - "install", - "--root", - ".", - "--locked", - { "--version", "42.13.37" }, - vim.NIL, -- --git - vim.NIL, -- --features - "my-crate", - } - end) - ) - - it( - "should call cargo install with git source", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, cargo.crate("my-crate", { git = { url = "https://my-crate.git" } })) - assert.spy(ctx.spawn.cargo).was_called(1) - assert.spy(ctx.spawn.cargo).was_called_with { - "install", - "--root", - ".", - "--locked", - vim.NIL, -- version - { "--git", "https://my-crate.git" }, - vim.NIL, -- --features - "my-crate", - } - end) - ) - - it( - "should call cargo install with git source and a specific crate", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, cargo.crate("crate-name", { git = { url = "https://my-crate.git" } })) - assert.spy(ctx.spawn.cargo).was_called(1) - assert.spy(ctx.spawn.cargo).was_called_with { - "install", - "--root", - ".", - "--locked", - vim.NIL, -- version - { "--git", "https://my-crate.git" }, - vim.NIL, -- --features - "crate-name", - } - end) - ) - - it( - "should respect options", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, cargo.crate("my-crate", { features = "lsp" })) - assert.spy(ctx.spawn.cargo).was_called(1) - assert.spy(ctx.spawn.cargo).was_called_with { - "install", - "--root", - ".", - "--locked", - { "--version", "42.13.37" }, - vim.NIL, -- --git - { "--features", "lsp" }, - "my-crate", - } - end) - ) - - it( - "should target tagged git crates", - async_test(function() - local handle = InstallHandleGenerator "dummy" - stub(github, "tag") - github.tag.returns { tag = "v2.1.1", with_receipt = mockx.just_runs } - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context( - ctx, - cargo.crate("my-crate", { - git = { - url = "https://github.com/crate/my-crate", - tag = true, - }, - features = "lsp", - }) - ) - assert.spy(ctx.spawn.cargo).was_called_with { - "install", - "--root", - ".", - "--locked", - { "--tag", "v2.1.1" }, - { "--git", "https://github.com/crate/my-crate" }, -- --git - { "--features", "lsp" }, - "my-crate", - } - end) - ) - - it( - "should provide receipt information", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, cargo.crate "main-package") - assert.same({ - type = "cargo", - package = "main-package", - }, ctx.receipt.primary_source) - end) - ) -end) - -describe("cargo version check", function() - it("parses cargo installed packages output", function() - assert.same( - { - ["bat"] = { name = "bat", version = "0.18.3" }, - ["exa"] = { name = "exa", version = "0.10.1" }, - ["git-select-branch"] = { name = "git-select-branch", version = "0.1.1" }, - ["hello_world"] = { name = "hello_world", version = "0.0.1" }, - ["rust-analyzer"] = { - name = "rust-analyzer", - version = "187bee0b", - github_ref = { owner = "rust-lang", repo = "rust-analyzer", ref = "187bee0b" }, - }, - ["move-analyzer"] = { - name = "move-analyzer", - version = "3cef7fa8", - github_ref = { owner = "move-language", repo = "move", ref = "3cef7fa8" }, - }, - ["stylua"] = { name = "stylua", version = "0.11.2" }, - ["zoxide"] = { name = "zoxide", version = "0.5.0" }, - }, - cargo.parse_installed_crates(_.dedent [[ - bat v0.18.3: - bat - exa v0.10.1: - exa - git-select-branch v0.1.1: - git-select-branch - hello_world v0.0.1 (/private/var/folders/ky/s6yyhm_d24d0jsrql4t8k4p40000gn/T/tmp.LGbguATJHj): - hello_world - move-analyzer v1.0.0 (https://github.com/move-language/move#3cef7fa8): - move-analyzer - rust-analyzer v0.0.0 (https://github.com/rust-lang/rust-analyzer?tag=2022-09-19#187bee0b): - rust-analyzer - stylua v0.11.2: - stylua - zoxide v0.5.0: - zoxide - ]]) - ) - end) - - it( - "should return current version", - async_test(function() - stub(spawn, "cargo") - spawn.cargo.returns(Result.success { - stdout = _.dedent [[ - flux-lsp v0.8.8 (https://github.com/influxdata/flux-lsp#4e452f07): - flux-lsp - ]], - }) - - local result = cargo.get_installed_primary_package_version( - mock.new { - primary_source = mock.new { - type = "cargo", - package = "https://github.com/influxdata/flux-lsp", - }, - }, - path.package_prefix "dummy" - ) - - assert.spy(spawn.cargo).was_called(1) - assert.spy(spawn.cargo).was_called_with(match.tbl_containing { - "install", - "--list", - "--root", - ".", - cwd = path.package_prefix "dummy", - }) - assert.is_true(result:is_success()) - assert.equals("4e452f07", result:get_or_nil()) - end) - ) - - it( - "should return outdated primary package", - async_test(function() - stub(spawn, "cargo") - spawn.cargo.returns(Result.success { - stdout = _.dedent [[ - lelwel v0.4.0: - lelwel-ls - ]], - }) - stub(cargo_client, "fetch_crate") - cargo_client.fetch_crate.returns(Result.success { - crate = { - id = "lelwel", - max_stable_version = "0.4.2", - max_version = "0.4.2", - newest_version = "0.4.2", - }, - }) - - local result = cargo.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "cargo", - package = "lelwel", - }, - }, - path.package_prefix "dummy" - ) - - assert.spy(spawn.cargo).was_called(1) - assert.spy(spawn.cargo).was_called_with(match.tbl_containing { - "install", - "--list", - "--root", - ".", - cwd = path.package_prefix "dummy", - }) - assert.is_true(result:is_success()) - assert.is_true(match.tbl_containing { - current_version = "0.4.0", - latest_version = "0.4.2", - name = "lelwel", - }(result:get_or_nil())) - end) - ) - - it( - "should recognize up-to-date crates", - async_test(function() - stub(spawn, "cargo") - spawn.cargo.returns(Result.success { - stdout = _.dedent [[ - lelwel v0.4.0: - lelwel-ls - ]], - }) - stub(cargo_client, "fetch_crate") - cargo_client.fetch_crate.returns(Result.success { - crate = { - id = "lelwel", - max_stable_version = "0.4.0", - max_version = "0.4.0", - newest_version = "0.4.0", - }, - }) - - local result = cargo.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "cargo", - package = "lelwel", - }, - }, - path.package_prefix "dummy" - ) - - assert.is_true(result:is_failure()) - assert.equals("Primary package is not outdated.", result:err_or_nil()) - end) - ) - - it( - "should return outdated primary package from git source", - async_test(function() - stub(spawn, "cargo") - spawn.cargo.returns(Result.success { - stdout = _.dedent [[ - move-analyzer v1.0.0 (https://github.com/move-language/move#3cef7fa8): - move-analyzer - ]], - }) - - stub(github_client, "fetch_commits") - github_client.fetch_commits - .on_call_with("move-language/move", { page = 1, per_page = 1 }) - .returns(Result.success { - { - sha = "b243f1fb", - }, - }) - - local result = cargo.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "cargo", - package = "move-analyzer", - }, - }, - path.package_prefix "dummy" - ) - - assert.spy(spawn.cargo).was_called(1) - assert.spy(spawn.cargo).was_called_with(match.tbl_containing { - "install", - "--list", - "--root", - ".", - cwd = path.package_prefix "dummy", - }) - assert.is_true(result:is_success()) - assert.is_true(match.tbl_containing { - current_version = "3cef7fa8", - latest_version = "b243f1fb", - name = "move-analyzer", - }(result:get_or_nil())) - end) - ) -end) diff --git a/tests/mason-core/managers/composer_spec.lua b/tests/mason-core/managers/composer_spec.lua deleted file mode 100644 index 5e5b97ba1..000000000 --- a/tests/mason-core/managers/composer_spec.lua +++ /dev/null @@ -1,173 +0,0 @@ -local Result = require "mason-core.result" -local composer = require "mason-core.managers.composer" -local installer = require "mason-core.installer" -local mock = require "luassert.mock" -local path = require "mason-core.path" -local spawn = require "mason-core.spawn" -local spy = require "luassert.spy" -local stub = require "luassert.stub" - -describe("composer manager", function() - it( - "should call composer require", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - ctx.fs.file_exists = spy.new(mockx.returns(false)) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context( - ctx, - composer.packages { "main-package", "supporting-package", "supporting-package2" } - ) - assert.spy(ctx.spawn.composer).was_called(2) - assert.spy(ctx.spawn.composer).was_called_with { - "init", - "--no-interaction", - "--stability=stable", - } - assert.spy(ctx.spawn.composer).was_called_with { - "require", - { - "main-package:42.13.37", - "supporting-package", - "supporting-package2", - }, - } - end) - ) - - it( - "should provide receipt information", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context( - ctx, - composer.packages { "main-package", "supporting-package", "supporting-package2" } - ) - assert.same({ - type = "composer", - package = "main-package", - }, ctx.receipt.primary_source) - assert.same({ - { - type = "composer", - package = "supporting-package", - }, - { - type = "composer", - package = "supporting-package2", - }, - }, ctx.receipt.secondary_sources) - end) - ) -end) - -describe("composer version check", function() - it( - "should return current version", - async_test(function() - stub(spawn, "composer") - spawn.composer.returns(Result.success { - stdout = [[ - { - "name": "vimeo/psalm", - "versions": [ - "4.0.0" - ] - } - ]], - }) - - local result = composer.get_installed_primary_package_version( - mock.new { - primary_source = mock.new { - type = "composer", - package = "vimeo/psalm", - }, - }, - path.package_prefix "dummy" - ) - - assert.spy(spawn.composer).was_called(1) - assert.spy(spawn.composer).was_called_with { - "info", - "--format=json", - "vimeo/psalm", - cwd = path.package_prefix "dummy", - } - assert.is_true(result:is_success()) - assert.equals("4.0.0", result:get_or_nil()) - end) - ) - - it( - "should return outdated primary package", - async_test(function() - stub(spawn, "composer") - spawn.composer.returns(Result.success { - stdout = [[ - { - "installed": [ - { - "name": "vimeo/psalm", - "version": "4.0.0", - "latest": "4.22.0", - "latest-status": "semver-safe-update", - "description": "A static analysis tool for finding errors in PHP applications" - } - ] - } - ]], - }) - - local result = composer.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "composer", - package = "vimeo/psalm", - }, - }, - path.package_prefix "dummy" - ) - - assert.spy(spawn.composer).was_called(1) - assert.spy(spawn.composer).was_called_with { - "outdated", - "--no-interaction", - "--format=json", - cwd = path.package_prefix "dummy", - } - assert.is_true(result:is_success()) - assert.same({ - name = "vimeo/psalm", - current_version = "4.0.0", - latest_version = "4.22.0", - }, result:get_or_nil()) - end) - ) - - it( - "should return failure if primary package is not outdated", - async_test(function() - stub(spawn, "composer") - spawn.composer.returns(Result.success { - stdout = [[{"installed": []}]], - }) - - local result = composer.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "composer", - package = "vimeo/psalm", - }, - }, - path.package_prefix "dummy" - ) - - assert.is_true(result:is_failure()) - assert.equals("Primary package is not outdated.", result:err_or_nil()) - end) - ) -end) diff --git a/tests/mason-core/managers/dotnet_spec.lua b/tests/mason-core/managers/dotnet_spec.lua deleted file mode 100644 index b6e0f8c0d..000000000 --- a/tests/mason-core/managers/dotnet_spec.lua +++ /dev/null @@ -1,38 +0,0 @@ -local dotnet = require "mason-core.managers.dotnet" -local installer = require "mason-core.installer" - -describe("dotnet manager", function() - it( - "should call dotnet tool update", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, dotnet.package "main-package") - assert.spy(ctx.spawn.dotnet).was_called(1) - assert.spy(ctx.spawn.dotnet).was_called_with { - "tool", - "update", - "--ignore-failed-sources", - "--tool-path", - ".", - { "--version", "42.13.37" }, - "main-package", - } - end) - ) - - it( - "should provide receipt information", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, dotnet.package "main-package") - assert.same({ - type = "dotnet", - package = "main-package", - }, ctx.receipt.primary_source) - end) - ) -end) diff --git a/tests/mason-core/managers/gem_spec.lua b/tests/mason-core/managers/gem_spec.lua deleted file mode 100644 index a99bf5e95..000000000 --- a/tests/mason-core/managers/gem_spec.lua +++ /dev/null @@ -1,190 +0,0 @@ -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local api = require "mason-registry.api" -local gem = require "mason-core.managers.gem" -local installer = require "mason-core.installer" -local match = require "luassert.match" -local mock = require "luassert.mock" -local spawn = require "mason-core.spawn" -local spy = require "luassert.spy" -local stub = require "luassert.stub" - -describe("gem manager", function() - it( - "should call gem install", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, gem.packages { "main-package", "supporting-package", "supporting-package2" }) - assert.spy(ctx.spawn.gem).was_called(1) - assert.spy(ctx.spawn.gem).was_called_with(match.tbl_containing { - "install", - "--no-user-install", - "--no-format-executable", - "--install-dir=.", - "--bindir=bin", - "--no-document", - match.tbl_containing { - "main-package:42.13.37", - "supporting-package", - "supporting-package2", - }, - }) - end) - ) - - it( - "should provide receipt information", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, gem.packages { "main-package", "supporting-package", "supporting-package2" }) - assert.same({ - type = "gem", - package = "main-package", - }, ctx.receipt.primary_source) - assert.same({ - { - type = "gem", - package = "supporting-package", - }, - { - type = "gem", - package = "supporting-package2", - }, - }, ctx.receipt.secondary_sources) - end) - ) -end) - -describe("gem version check", function() - it( - "should return current version", - async_test(function() - stub(spawn, "gem") - spawn.gem.returns(Result.success { - stdout = _.dedent [[ - shellwords (default: 0.1.0) - singleton (default: 0.1.1) - solargraph (0.44.0) - stringio (default: 3.0.1) - strscan (default: 3.0.1) - ]], - }) - - local result = gem.get_installed_primary_package_version( - mock.new { - primary_source = mock.new { - type = "gem", - package = "solargraph", - }, - }, - "/tmp/install/dir" - ) - - assert.spy(spawn.gem).was_called(1) - assert.spy(spawn.gem).was_called_with(match.tbl_containing { - "list", - cwd = "/tmp/install/dir", - env = match.tbl_containing { - GEM_HOME = "/tmp/install/dir", - GEM_PATH = "/tmp/install/dir", - PATH = match.matches "^/tmp/install/dir/bin:.*$", - }, - }) - assert.is_true(result:is_success()) - assert.equals("0.44.0", result:get_or_nil()) - end) - ) - - it( - "should return outdated primary package", - async_test(function() - stub(spawn, "gem") - spawn.gem.returns(Result.success { - stdout = _.dedent [[ - shellwords (default: 0.1.0) - singleton (default: 0.1.1) - solargraph (0.44.0) - stringio (default: 3.0.1) - strscan (default: 3.0.1) - ]], - }) - stub(api, "get") - api.get.on_call_with("/api/rubygems/solargraph/versions/latest").returns(Result.success { - name = "solargraph", - version = "0.44.3", - }) - - local result = gem.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "gem", - package = "solargraph", - }, - }, - "/tmp/install/dir" - ) - - assert.is_true(result:is_success()) - assert.same({ - name = "solargraph", - current_version = "0.44.0", - latest_version = "0.44.3", - }, result:get_or_nil()) - end) - ) - - it( - "should return failure if primary package is not outdated", - async_test(function() - stub(spawn, "gem") - spawn.gem.returns(Result.success { - stdout = _.dedent [[ - shellwords (default: 0.1.0) - singleton (default: 0.1.1) - solargraph (0.44.0) - stringio (default: 3.0.1) - strscan (default: 3.0.1) - ]], - }) - stub(api, "get") - api.get.on_call_with("/api/rubygems/solargraph/versions/latest").returns(Result.success { - name = "solargraph", - version = "0.44.0", - }) - - local result = gem.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "gem", - package = "solargraph", - }, - }, - "/tmp/install/dir" - ) - - assert.is_true(result:is_failure()) - assert.equals("Primary package is not outdated.", result:err_or_nil()) - end) - ) - - it("should parse gem list output", function() - assert.same( - { - ["solargraph"] = "0.44.3", - ["unicode-display_width"] = "2.1.0", - }, - gem.parse_gem_list_output [[ - -*** LOCAL GEMS *** - -nokogiri (1.13.3 arm64-darwin) -solargraph (0.44.3) -unicode-display_width (2.1.0) -]] - ) - end) -end) diff --git a/tests/mason-core/managers/git_spec.lua b/tests/mason-core/managers/git_spec.lua deleted file mode 100644 index 50d2470f9..000000000 --- a/tests/mason-core/managers/git_spec.lua +++ /dev/null @@ -1,181 +0,0 @@ -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local installer = require "mason-core.installer" -local mock = require "luassert.mock" -local spawn = require "mason-core.spawn" -local stub = require "luassert.stub" - -local git = require "mason-core.managers.git" - -describe("git manager", function() - it( - "should fail if no git repo provided", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - local err = assert.has_error(function() - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, function() - git.clone {} - end) - end) - assert.equals("No git URL provided.", err) - assert.spy(ctx.spawn.git).was_not_called() - end) - ) - - it( - "should clone provided repo", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, function() - git.clone { "https://github.com/williamboman/mason.nvim.git" } - end) - assert.spy(ctx.spawn.git).was_called(1) - assert.spy(ctx.spawn.git).was_called_with { - "clone", - "--depth", - "1", - vim.NIL, - "https://github.com/williamboman/mason.nvim.git", - ".", - } - end) - ) - - it( - "should fetch and checkout revision if requested", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "1337" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, function() - git.clone { "https://github.com/williamboman/mason.nvim.git" } - end) - assert.spy(ctx.spawn.git).was_called(3) - assert.spy(ctx.spawn.git).was_called_with { - "clone", - "--depth", - "1", - vim.NIL, - "https://github.com/williamboman/mason.nvim.git", - ".", - } - assert.spy(ctx.spawn.git).was_called_with { - "fetch", - "--depth", - "1", - "origin", - "1337", - } - assert.spy(ctx.spawn.git).was_called_with { "checkout", "FETCH_HEAD" } - end) - ) - - it( - "should provide receipt information", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, function() - git.clone({ "https://github.com/williamboman/mason.nvim.git" }).with_receipt() - end) - assert.same({ - type = "git", - remote = "https://github.com/williamboman/mason.nvim.git", - }, ctx.receipt.primary_source) - assert.is_true(#ctx.receipt.secondary_sources == 0) - end) - ) -end) - -describe("git version check", function() - it( - "should return current version", - async_test(function() - stub(spawn, "git") - spawn.git.returns(Result.success { - stdout = [[19c668c]], - }) - - local result = git.get_installed_revision({ type = "git" }, "/tmp/install/dir") - - assert.spy(spawn.git).was_called(1) - assert.spy(spawn.git).was_called_with { "rev-parse", "--short", "HEAD", cwd = "/tmp/install/dir" } - assert.is_true(result:is_success()) - assert.equals("19c668c", result:get_or_nil()) - end) - ) - - it( - "should check for outdated git clone", - async_test(function() - stub(spawn, "git") - spawn.git.returns(Result.success { - stdout = _.dedent [[ - 728307a74cd5f2dec7ca2ca164785c25673d6328 - 19c668cd10695b243b09452f0dfd53570c1a2e7d - ]], - }) - - local result = git.check_outdated_git_clone( - mock.new { - primary_source = mock.new { - type = "git", - remote = "https://github.com/williamboman/mason.nvim.git", - }, - }, - "/tmp/install/dir" - ) - - assert.spy(spawn.git).was_called(2) - assert.spy(spawn.git).was_called_with { - "fetch", - "origin", - "HEAD", - cwd = "/tmp/install/dir", - } - assert.spy(spawn.git).was_called_with { - "rev-parse", - "FETCH_HEAD", - "HEAD", - cwd = "/tmp/install/dir", - } - assert.is_true(result:is_success()) - assert.same({ - name = "https://github.com/williamboman/mason.nvim.git", - current_version = "19c668cd10695b243b09452f0dfd53570c1a2e7d", - latest_version = "728307a74cd5f2dec7ca2ca164785c25673d6328", - }, result:get_or_nil()) - end) - ) - - it( - "should return failure if clone is not outdated", - async_test(function() - stub(spawn, "git") - spawn.git.returns(Result.success { - stdout = _.dedent [[ - 19c668cd10695b243b09452f0dfd53570c1a2e7d - 19c668cd10695b243b09452f0dfd53570c1a2e7d - ]], - }) - - local result = git.check_outdated_git_clone( - mock.new { - primary_source = mock.new { - type = "git", - remote = "https://github.com/williamboman/mason.nvim.git", - }, - }, - "/tmp/install/dir" - ) - - assert.is_true(result:is_failure()) - assert.equals("Git clone is up to date.", result:err_or_nil()) - end) - ) -end) diff --git a/tests/mason-core/managers/github_client_spec.lua b/tests/mason-core/managers/github_client_spec.lua deleted file mode 100644 index d07a0e742..000000000 --- a/tests/mason-core/managers/github_client_spec.lua +++ /dev/null @@ -1,23 +0,0 @@ -local Result = require "mason-core.result" -local client = require "mason-core.managers.github.client" -local spawn = require "mason-core.spawn" -local stub = require "luassert.stub" - -describe("github client", function() - it("should provide query parameters in api calls", function() - stub(spawn, "gh") - spawn.gh.returns(Result.success { stdout = "response data" }) - client.api_call("repos/some/repo", { - params = { - page = 23, - page_limit = 82, - }, - }) - assert.spy(spawn.gh).was_called(1) - assert.spy(spawn.gh).was_called_with { - "api", - "repos/some/repo?page=23&page_limit=82", - env = { CLICOLOR_FORCE = 0 }, - } - end) -end) diff --git a/tests/mason-core/managers/github_spec.lua b/tests/mason-core/managers/github_spec.lua deleted file mode 100644 index 852c3ff6a..000000000 --- a/tests/mason-core/managers/github_spec.lua +++ /dev/null @@ -1,103 +0,0 @@ -local mock = require "luassert.mock" -local stub = require "luassert.stub" - -local Optional = require "mason-core.optional" -local Result = require "mason-core.result" -local github = require "mason-core.managers.github" -local installer = require "mason-core.installer" -local providers = require "mason-core.providers" - -describe("github release file", function() - it( - "should use provided version", - async_test(function() - stub(providers.github, "get_latest_release") - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - local source = installer.exec_in_context(ctx, function() - return github.release_file { - repo = "williamboman/mason.nvim", - asset_file = "program.exe", - version = Optional.of "13.37", - } - end) - assert.spy(providers.github.get_latest_release).was_not_called() - assert.equals("13.37", source.release) - assert.equals( - "https://github.com/williamboman/mason.nvim/releases/download/13.37/program.exe", - source.download_url - ) - end) - ) - - it( - "should use use dynamic asset_file", - async_test(function() - stub(providers.github, "get_latest_release") - providers.github.get_latest_release.returns(Result.success(mock.new { - tag_name = "im_the_tag", - })) - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - local source = installer.exec_in_context(ctx, function() - return github.release_file { - repo = "williamboman/mason.nvim", - asset_file = function(version) - return version .. "_for_reals" - end, - } - end) - assert.spy(providers.github.get_latest_release).was_called(1) - assert.spy(providers.github.get_latest_release).was_called_with "williamboman/mason.nvim" - assert.equals("im_the_tag", source.release) - assert.equals("im_the_tag_for_reals", source.asset_file) - assert.equals( - "https://github.com/williamboman/mason.nvim/releases/download/im_the_tag/im_the_tag_for_reals", - source.download_url - ) - end) - ) -end) - -describe("github release version", function() - it( - "should use provided version", - async_test(function() - stub(providers.github, "get_latest_release") - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - local source = installer.exec_in_context(ctx, function() - return github.release_version { - repo = "williamboman/mason.nvim", - version = Optional.of "13.37", - } - end) - assert.spy(providers.github.get_latest_release).was_not_called() - assert.equals("13.37", source.release) - end) - ) - - it( - "should fetch latest release from GitHub API", - async_test(function() - async_test(function() - stub(providers.github, "get_latest_release") - providers.github.get_latest_release.returns(Result.success { tag_name = "v42" }) - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - local source = installer.exec_in_context(ctx, function() - return github.release_version { - repo = "williamboman/mason.nvim", - } - end) - assert.spy(providers.github.get_latest_release).was_called(1) - assert.spy(providers.github.get_latest_release).was_called_with "williamboman/mason.nvim" - assert.equals("v42", source.release) - end) - end) - ) -end) diff --git a/tests/mason-core/managers/go_spec.lua b/tests/mason-core/managers/go_spec.lua deleted file mode 100644 index fd3ca0431..000000000 --- a/tests/mason-core/managers/go_spec.lua +++ /dev/null @@ -1,171 +0,0 @@ -local Result = require "mason-core.result" -local go = require "mason-core.managers.go" -local installer = require "mason-core.installer" -local mock = require "luassert.mock" -local path = require "mason-core.path" -local spawn = require "mason-core.spawn" -local spy = require "luassert.spy" -local stub = require "luassert.stub" - -describe("go manager", function() - it( - "should call go install", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, go.packages { "main-package", "supporting-package", "supporting-package2" }) - assert.spy(ctx.spawn.go).was_called(3) - assert.spy(ctx.spawn.go).was_called_with { - "install", - "-v", - "main-package@42.13.37", - env = { GOBIN = path.package_build_prefix "dummy" }, - } - assert.spy(ctx.spawn.go).was_called_with { - "install", - "-v", - "supporting-package@latest", - env = { GOBIN = path.package_build_prefix "dummy" }, - } - assert.spy(ctx.spawn.go).was_called_with { - "install", - "-v", - "supporting-package2@latest", - env = { GOBIN = path.package_build_prefix "dummy" }, - } - end) - ) - - it( - "should provide receipt information", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, go.packages { "main-package", "supporting-package", "supporting-package2" }) - assert.same({ - type = "go", - package = "main-package", - }, ctx.receipt.primary_source) - assert.same({ - { - type = "go", - package = "supporting-package", - }, - { - type = "go", - package = "supporting-package2", - }, - }, ctx.receipt.secondary_sources) - end) - ) -end) - -describe("go version check", function() - local go_version_output = [[ -gopls: go1.18 - path golang.org/x/tools/cmd - mod golang.org/x/tools/cmd v0.8.1 h1:q5nDpRopYrnF4DN/1o8ZQ7Oar4Yd4I5OtGMx5RyV2/8= - dep github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= - dep mvdan.cc/xurls/v2 v2.4.0 h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc= - build -compiler=gc - build GOOS=darwin -]] - - it("should parse go version output", function() - local parsed = go.parse_mod_version_output(go_version_output) - assert.same({ - path = { ["golang.org/x/tools/cmd"] = "" }, - mod = { ["golang.org/x/tools/cmd"] = "v0.8.1" }, - dep = { ["github.com/google/go-cmp"] = "v0.5.7", ["mvdan.cc/xurls/v2"] = "v2.4.0" }, - build = { ["-compiler=gc"] = "", ["GOOS=darwin"] = "" }, - }, parsed) - end) - - it( - "should return current version", - async_test(function() - stub(spawn, "go") - spawn.go.returns(Result.success { stdout = go_version_output }) - - local result = go.get_installed_primary_package_version( - mock.new { - primary_source = mock.new { - type = "go", - package = "golang.org/x/tools/cmd/gopls/...", - }, - }, - path.package_prefix "dummy" - ) - - assert.spy(spawn.go).was_called(1) - assert.spy(spawn.go).was_called_with { - "version", - "-m", - "gopls", - cwd = path.package_prefix "dummy", - } - assert.is_true(result:is_success()) - assert.equals("v0.8.1", result:get_or_nil()) - end) - ) - - it( - "should return outdated primary package", - async_test(function() - stub(spawn, "go") - spawn.go - .on_call_with({ - "list", - "-json", - "-m", - "golang.org/x/tools/cmd@latest", - cwd = path.package_prefix "dummy", - }) - .returns(Result.success { - stdout = ([[ - { - "Path": %q, - "Version": "v2.0.0" - } - ]]):format(path.package_prefix "dummy"), - }) - spawn.go - .on_call_with({ - "version", - "-m", - "gopls", - cwd = path.package_prefix "dummy", - }) - .returns(Result.success { - stdout = go_version_output, - }) - - local result = go.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "go", - package = "golang.org/x/tools/cmd/gopls/...", - }, - }, - path.package_prefix "dummy" - ) - - assert.is_true(result:is_success()) - assert.same({ - name = "golang.org/x/tools/cmd", - current_version = "v0.8.1", - latest_version = "v2.0.0", - }, result:get_or_nil()) - end) - ) - - it("should parse package mod names", function() - assert.equals("github.com/cweill/gotests", go.parse_package_mod "github.com/cweill/gotests/...") - assert.equals("golang.org/x/tools/gopls", go.parse_package_mod "golang.org/x/tools/gopls/...") - assert.equals("golang.org/x/crypto", go.parse_package_mod "golang.org/x/crypto/...") - assert.equals("github.com/go-delve/delve", go.parse_package_mod "github.com/go-delve/delve/cmd/dlv") - assert.equals("mvdan.cc/sh/v3", go.parse_package_mod "mvdan.cc/sh/v3/cmd/shfmt") - end) -end) diff --git a/tests/mason-core/managers/luarocks_spec.lua b/tests/mason-core/managers/luarocks_spec.lua deleted file mode 100644 index d89f65fa9..000000000 --- a/tests/mason-core/managers/luarocks_spec.lua +++ /dev/null @@ -1,139 +0,0 @@ -local a = require "mason-core.async" -local installer = require "mason-core.installer" -local luarocks = require "mason-core.managers.luarocks" -local path = require "mason-core.path" - -describe("luarocks manager", function() - before_each(function() - a.run_blocking(installer.create_prefix_dirs) - end) - - it( - "should install provided package", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, luarocks.package "lua-cjson") - assert.spy(ctx.spawn.luarocks).was_called(1) - assert.spy(ctx.spawn.luarocks).was_called_with { - "install", - "--tree", - path.package_prefix "dummy", - vim.NIL, -- --dev flag - vim.NIL, -- --server flag - "lua-cjson", - vim.NIL, -- version - } - end) - ) - - it( - "should install provided version", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "1.2.3" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, luarocks.package "lua-cjson") - assert.spy(ctx.spawn.luarocks).was_called(1) - assert.spy(ctx.spawn.luarocks).was_called_with { - "install", - "--tree", - path.package_prefix "dummy", - vim.NIL, -- --dev flag - vim.NIL, -- --server flag - "lua-cjson", - "1.2.3", - } - end) - ) - - it( - "should provide --dev flag", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, luarocks.package("lua-cjson", { dev = true })) - assert.spy(ctx.spawn.luarocks).was_called(1) - assert.spy(ctx.spawn.luarocks).was_called_with { - "install", - "--tree", - path.package_prefix "dummy", - "--dev", - vim.NIL, -- --server flag - "lua-cjson", - vim.NIL, -- version - } - end) - ) - - it( - "should provide --server flag", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, luarocks.package("luaformatter", { server = "https://luarocks.org/dev" })) - assert.spy(ctx.spawn.luarocks).was_called(1) - assert.spy(ctx.spawn.luarocks).was_called_with { - "install", - "--tree", - path.package_prefix "dummy", - vim.NIL, -- --dev flag - "--server=https://luarocks.org/dev", - "luaformatter", - vim.NIL, -- version - } - end) - ) - - it("should parse outdated luarocks", function() - assert.same( - { - { - name = "lua-cjson", - installed = "2.1.0-1", - available = "2.1.0.6-1", - repo = "https://luarocks.org", - }, - { - name = "lua-resty-influx-mufanh", - installed = "0.2.1-0", - available = "0.2.1-1", - repo = "https://luarocks.org", - }, - }, - luarocks.parse_outdated_rocks [[lua-cjson 2.1.0-1 2.1.0.6-1 https://luarocks.org -lua-resty-influx-mufanh 0.2.1-0 0.2.1-1 https://luarocks.org]] - ) - end) - - it("should parse listed luarocks", function() - assert.same( - { - { - package = "lua-cjson", - version = "2.1.0-1", - arch = "installed", - nrepo = "/my/luarock/loc", - }, - { - package = "lua-resty-http", - version = "0.17.0.beta.1-0", - arch = "installed", - nrepo = "/my/luarock/loc", - }, - { - package = "lua-resty-influx-mufanh", - version = "0.2.1-0", - arch = "installed", - nrepo = "/my/luarock/loc", - }, - }, - luarocks.parse_installed_rocks [[lua-cjson 2.1.0-1 installed /my/luarock/loc -lua-resty-http 0.17.0.beta.1-0 installed /my/luarock/loc -lua-resty-influx-mufanh 0.2.1-0 installed /my/luarock/loc]] - ) - end) -end) diff --git a/tests/mason-core/managers/npm_spec.lua b/tests/mason-core/managers/npm_spec.lua deleted file mode 100644 index 31500ac7a..000000000 --- a/tests/mason-core/managers/npm_spec.lua +++ /dev/null @@ -1,206 +0,0 @@ -local Result = require "mason-core.result" -local api = require "mason-registry.api" -local installer = require "mason-core.installer" -local match = require "luassert.match" -local mock = require "luassert.mock" -local npm = require "mason-core.managers.npm" -local path = require "mason-core.path" -local spawn = require "mason-core.spawn" -local spy = require "luassert.spy" -local stub = require "luassert.stub" - -describe("npm manager", function() - it( - "should call npm install", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, npm.packages { "main-package", "supporting-package", "supporting-package2" }) - assert.spy(ctx.spawn.npm).was_called_with(match.tbl_containing { - "install", - match.tbl_containing { - "main-package@42.13.37", - "supporting-package", - "supporting-package2", - }, - }) - end) - ) - - it( - "should call npm init if node_modules and package.json doesnt exist", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - ctx.fs.file_exists = mockx.returns(false) - ctx.fs.dir_exists = mockx.returns(false) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, function() - npm.install { "main-package", "supporting-package", "supporting-package2" } - end) - assert.spy(ctx.spawn.npm).was_called_with { - "init", - "--yes", - "--scope=mason", - } - end) - ) - - it( - "should append .npmrc file", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - ctx.fs.append_file = spy.new(mockx.just_runs()) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, npm.packages { "main-package", "supporting-package", "supporting-package2" }) - assert.spy(ctx.fs.append_file).was_called(1) - assert.spy(ctx.fs.append_file).was_called_with(ctx.fs, ".npmrc", "global-style=true") - end) - ) - - it( - "should provide receipt information", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, npm.packages { "main-package", "supporting-package", "supporting-package2" }) - assert.same({ - type = "npm", - package = "main-package", - }, ctx.receipt.primary_source) - assert.same({ - { - type = "npm", - package = "supporting-package", - }, - { - type = "npm", - package = "supporting-package2", - }, - }, ctx.receipt.secondary_sources) - end) - ) -end) - -describe("npm version check", function() - it( - "should return current version", - async_test(function() - stub(spawn, "npm", function() - return Result.success { - stdout = [[ - { - "name": "bash", - "dependencies": { - "bash-language-server": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bash-language-server/-/bash-language-server-2.0.0.tgz" - } - } - } - ]], - } - end) - - local result = npm.get_installed_primary_package_version( - mock.new { - primary_source = mock.new { - type = "npm", - package = "bash-language-server", - }, - }, - path.package_prefix "dummy" - ) - - assert.spy(spawn.npm).was_called(1) - assert.spy(spawn.npm).was_called_with { "ls", "--json", cwd = path.package_prefix "dummy" } - assert.is_true(result:is_success()) - assert.equals("2.0.0", result:get_or_nil()) - end) - ) - - it( - "should return outdated primary package", - async_test(function() - stub(api, "get") - api.get.on_call_with("/api/npm/bash-language-server/versions/latest").returns(Result.success { - name = "bash-language-server", - version = "2.0.0", - }) - stub(spawn, "npm", function() - return Result.success { - stdout = [[ - { - "name": "bash", - "dependencies": { - "bash-language-server": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/bash-language-server/-/bash-language-server-1.17.0.tgz" - } - } - } - ]], - } - end) - - local result = npm.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "npm", - package = "bash-language-server", - }, - }, - path.package_prefix "dummy" - ) - - assert.is_true(result:is_success()) - assert.same({ - name = "bash-language-server", - current_version = "1.17.0", - latest_version = "2.0.0", - }, result:get_or_nil()) - end) - ) - - it( - "should return failure if primary package is not outdated", - async_test(function() - stub(spawn, "npm", function() - return Result.success { - stdout = [[ - { - "name": "bash", - "dependencies": { - "bash-language-server": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/bash-language-server/-/bash-language-server-1.17.0.tgz" - } - } - } - ]], - } - end) - stub(api, "get") - api.get.on_call_with("/api/npm/bash-language-server/versions/latest").returns(Result.success { - name = "bash-language-server", - version = "1.17.0", - }) - - local result = npm.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "npm", - package = "bash-language-server", - }, - }, - path.package_prefix "dummy" - ) - - assert.is_true(result:is_failure()) - assert.equals("Primary package is not outdated.", result:err_or_nil()) - end) - ) -end) diff --git a/tests/mason-core/managers/opam_spec.lua b/tests/mason-core/managers/opam_spec.lua deleted file mode 100644 index 297f9d8be..000000000 --- a/tests/mason-core/managers/opam_spec.lua +++ /dev/null @@ -1,57 +0,0 @@ -local installer = require "mason-core.installer" -local match = require "luassert.match" -local opam = require "mason-core.managers.opam" - -describe("opam manager", function() - it( - "should call opam install", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context( - ctx, - opam.packages { "main-package", "supporting-package", "supporting-package2" } - ) - assert.spy(ctx.spawn.opam).was_called(1) - assert.spy(ctx.spawn.opam).was_called_with(match.tbl_containing { - "install", - "--destdir=.", - "--yes", - "--verbose", - match.tbl_containing { - "main-package.42.13.37", - "supporting-package", - "supporting-package2", - }, - }) - end) - ) - - it( - "should provide receipt information", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context( - ctx, - opam.packages { "main-package", "supporting-package", "supporting-package2" } - ) - assert.same({ - type = "opam", - package = "main-package", - }, ctx.receipt.primary_source) - assert.same({ - { - type = "opam", - package = "supporting-package", - }, - { - type = "opam", - package = "supporting-package2", - }, - }, ctx.receipt.secondary_sources) - end) - ) -end) diff --git a/tests/mason-core/managers/pip3_spec.lua b/tests/mason-core/managers/pip3_spec.lua deleted file mode 100644 index 9ad8e1d83..000000000 --- a/tests/mason-core/managers/pip3_spec.lua +++ /dev/null @@ -1,284 +0,0 @@ -local mock = require "luassert.mock" -local path = require "mason-core.path" -local spy = require "luassert.spy" -local stub = require "luassert.stub" - -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local a = require "mason-core.async" -local api = require "mason-registry.api" -local installer = require "mason-core.installer" -local pip3 = require "mason-core.managers.pip3" -local settings = require "mason.settings" -local spawn = require "mason-core.spawn" - -describe("pip3 manager", function() - before_each(function() - settings.set(settings._DEFAULT_SETTINGS) - a.run_blocking(installer.create_prefix_dirs) - end) - - it("normalizes pip3 packages", function() - local normalize = pip3.normalize_package - assert.equals("python-lsp-server", normalize "python-lsp-server[all]") - assert.equals("python-lsp-server", normalize "python-lsp-server[]") - assert.equals("python-lsp-server", normalize "python-lsp-server[[]]") - end) - - it( - "should create venv and call pip3 install", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context( - ctx, - pip3.packages { "main-package", "supporting-package", "supporting-package2" } - ) - assert.equals(path.package_prefix "dummy", ctx.cwd:get()) -- should've promoted cwd - assert.spy(ctx.spawn.python3).was_called(1) - assert.spy(ctx.spawn.python3).was_called_with { - "-m", - "venv", - "venv", - } - assert.spy(ctx.spawn.python).was_called(1) - assert.spy(ctx.spawn.python).was_called_with { - "-m", - "pip", - "--disable-pip-version-check", - "install", - "-U", - {}, - { - "main-package==42.13.37", - "supporting-package", - "supporting-package2", - }, - with_paths = { path.concat { path.package_prefix "dummy", "venv", "bin" } }, - } - end) - ) - - it( - "should exhaust python3 executable candidates if all fail", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - ctx.spawn.python3 = spy.new(mockx.throws()) - ctx.spawn.python = spy.new(mockx.throws()) - local err = assert.has_error(function() - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, pip3.packages { "package" }) - end) - - assert.equals("Unable to create python3 venv environment.", err) - assert.spy(ctx.spawn.python3).was_called(1) - assert.spy(ctx.spawn.python).was_called(1) - end) - ) - - it( - "should use install_args from settings", - async_test(function() - settings.set { - pip = { - install_args = { "--proxy", "http://localhost:8080" }, - }, - } - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, pip3.packages { "package" }) - assert.spy(ctx.spawn.python).was_called(1) - assert.spy(ctx.spawn.python).was_called_with { - "-m", - "pip", - "--disable-pip-version-check", - "install", - "-U", - { "--proxy", "http://localhost:8080" }, - { "package" }, - with_paths = { path.concat { path.package_prefix "dummy", "venv", "bin" } }, - } - end) - ) - - it( - "should upgrade pip", - async_test(function() - settings.set { - pip = { - upgrade_pip = true, - }, - } - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context(ctx, pip3.packages { "package" }) - assert.spy(ctx.spawn.python).was_called(2) - assert.spy(ctx.spawn.python).was_called_with { - "-m", - "pip", - "--disable-pip-version-check", - "install", - "-U", - {}, - "pip", - with_paths = { path.concat { path.package_prefix "dummy", "venv", "bin" } }, - } - assert.spy(ctx.spawn.python).was_called_with { - "-m", - "pip", - "--disable-pip-version-check", - "install", - "-U", - {}, - { "package" }, - with_paths = { path.concat { path.package_prefix "dummy", "venv", "bin" } }, - } - end) - ) - - it( - "should provide receipt information", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle, { version = "42.13.37" }) - installer.prepare_installer(ctx):get_or_throw() - installer.exec_in_context( - ctx, - pip3.packages { "main-package", "supporting-package", "supporting-package2" } - ) - assert.same({ - type = "pip3", - package = "main-package", - }, ctx.receipt.primary_source) - assert.same({ - { - type = "pip3", - package = "supporting-package", - }, - { - type = "pip3", - package = "supporting-package2", - }, - }, ctx.receipt.secondary_sources) - end) - ) -end) - -describe("pip3 version check", function() - it( - "should return current version", - async_test(function() - stub(spawn, "python") - spawn.python.returns(Result.success { - stdout = _.dedent [[ - [ - {"name": "astroid", "version": "2.9.3"}, - {"name": "mccabe", "version": "0.6.1"}, - {"name": "python-lsp-server", "version": "1.3.0", "latest_version": "1.4.0", "latest_filetype": "wheel"}, - {"name": "wrapt", "version": "1.13.3", "latest_version": "1.14.0", "latest_filetype": "wheel"} - ] - ]], - }) - - local result = pip3.get_installed_primary_package_version( - mock.new { - primary_source = mock.new { - type = "pip3", - package = "python-lsp-server", - }, - }, - path.package_prefix "dummy" - ) - - assert.spy(spawn.python).was_called(1) - assert.spy(spawn.python).was_called_with { - "-m", - "pip", - "list", - "--format=json", - cwd = path.package_prefix "dummy", - with_paths = { path.concat { path.package_prefix "dummy", "venv", "bin" } }, - } - assert.is_true(result:is_success()) - assert.equals("1.3.0", result:get_or_nil()) - end) - ) - - it( - "should return outdated primary package", - async_test(function() - stub(api, "get") - api.get.on_call_with("/api/pypi/python-lsp-server/versions/latest").returns(Result.success { - name = "python-lsp-server", - version = "1.4.0", - }) - stub(spawn, "python") - spawn.python.returns(Result.success { - stdout = [[ - [ - {"name": "astroid", "version": "2.9.3"}, - {"name": "mccabe", "version": "0.6.1"}, - {"name": "python-lsp-server", "version": "1.3.0", "latest_version": "1.4.0", "latest_filetype": "wheel"}, - {"name": "wrapt", "version": "1.13.3", "latest_version": "1.14.0", "latest_filetype": "wheel"} - ] - ]], - }) - - local result = pip3.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "pip3", - package = "python-lsp-server", - }, - }, - path.package_prefix "dummy" - ) - - assert.is_true(result:is_success()) - assert.same({ - name = "python-lsp-server", - current_version = "1.3.0", - latest_version = "1.4.0", - }, result:get_or_nil()) - end) - ) - - it( - "should return failure if primary package is not outdated", - async_test(function() - stub(spawn, "python") - spawn.python.returns(Result.success { - stdout = [[ - [ - {"name": "astroid", "version": "2.9.3"}, - {"name": "mccabe", "version": "0.6.1"}, - {"name": "python-lsp-server", "version": "1.3.0", "latest_version": "1.4.0", "latest_filetype": "wheel"}, - {"name": "wrapt", "version": "1.13.3", "latest_version": "1.14.0", "latest_filetype": "wheel"} - ] - ]], - }) - stub(api, "get") - api.get.on_call_with("/api/pypi/python-lsp-server/versions/latest").returns(Result.success { - name = "python-lsp-server", - version = "1.3.0", - }) - - local result = pip3.check_outdated_primary_package( - mock.new { - primary_source = mock.new { - type = "pip3", - package = "python-lsp-server", - }, - }, - "/tmp/install/dir" - ) - - assert.is_true(result:is_failure()) - assert.equals("Primary package is not outdated.", result:err_or_nil()) - end) - ) -end)