diff options
25 files changed, 637 insertions, 264 deletions
diff --git a/lua/mason-core/installer/context.lua b/lua/mason-core/installer/context.lua index b12b540f..7637209f 100644 --- a/lua/mason-core/installer/context.lua +++ b/lua/mason-core/installer/context.lua @@ -241,7 +241,7 @@ function InstallContext:promote_cwd() end ---@param rel_path string The relative path from the current working directory to change cwd to. Will only restore to the initial cwd after execution of fn (if provided). ----@param fn async (fun())? The function to run in the context of the given path. +---@param fn async (fun(): any)? The function to run in the context of the given path. function InstallContext:chdir(rel_path, fn) local old_cwd = self.cwd:get() self.cwd:set(path.concat { old_cwd, rel_path }) diff --git a/lua/mason-core/installer/managers/build.lua b/lua/mason-core/installer/managers/build.lua deleted file mode 100644 index a1549a28..00000000 --- a/lua/mason-core/installer/managers/build.lua +++ /dev/null @@ -1,48 +0,0 @@ -local _ = require "mason-core.functional" -local a = require "mason-core.async" -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 M = {} - ----@class BuildInstruction ----@field target? Platform | Platform[] ----@field run string ----@field staged? boolean ----@field env? table<string, string> - ----@async ----@param build BuildInstruction ----@return Result ----@nodiscard -function M.run(build) - log.fmt_debug("build: run %s", build) - local ctx = installer.context() - if build.staged == false then - ctx:promote_cwd() - end - return platform.when { - unix = function() - return ctx.spawn.bash { - on_spawn = a.scope(function(_, stdio) - local stdin = stdio[1] - async_uv.write(stdin, "set -euxo pipefail;\n") - async_uv.write(stdin, build.run) - async_uv.shutdown(stdin) - async_uv.close(stdin) - end), - env = build.env, - } - end, - win = function() - return powershell.command(build.run, { - env = build.env, - }, ctx.spawn) - end, - } -end - -return M diff --git a/lua/mason-core/installer/managers/common.lua b/lua/mason-core/installer/managers/common.lua new file mode 100644 index 00000000..c730a3aa --- /dev/null +++ b/lua/mason-core/installer/managers/common.lua @@ -0,0 +1,126 @@ +local Result = require "mason-core.result" +local _ = require "mason-core.functional" +local a = require "mason-core.async" +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 std = require "mason-core.installer.managers.std" + +local M = {} + +---@class DownloadItem +---@field download_url string +---@field out_file string + +---@class FileDownloadSpec +---@field file string | string[] + +local get_source_file = _.compose(_.head, _.split ":") +local get_outfile = _.compose(_.last, _.split ":") + +---Normalizes file paths from e.g. "file:out-dir/" to "out-dir/file". +---@param file string +local function normalize_file_path(file) + local source_file = get_source_file(file) + local new_path = get_outfile(file) + + -- a dir expression (e.g. "libexec/") + if _.matches("/$", new_path) then + return new_path .. source_file + end + return new_path +end + +---@generic T : FileDownloadSpec +---@type fun(download: T): T +M.normalize_files = _.evolve { + file = _.cond { + { _.is "string", normalize_file_path }, + { _.T, _.map(normalize_file_path) }, + }, +} + +---@param download FileDownloadSpec +---@param url_generator fun(file: string): string +---@return DownloadItem[] +function M.parse_downloads(download, url_generator) + local files = download.file + if type(files) == "string" then + files = { files } + end + + return _.map(function(file) + local source_file = get_source_file(file) + local out_file = normalize_file_path(file) + return { + download_url = url_generator(source_file), + out_file = out_file, + } + end, files) +end + +---@async +---@param ctx InstallContext +---@param downloads DownloadItem[] +---@nodiscard +function M.download_files(ctx, downloads) + return Result.try(function(try) + for __, download in ipairs(downloads) do + a.scheduler() + local out_dir = vim.fn.fnamemodify(download.out_file, ":h") + local out_file = vim.fn.fnamemodify(download.out_file, ":t") + if out_dir ~= "." then + try(Result.pcall(function() + ctx.fs:mkdirp(out_dir) + end)) + end + try(ctx:chdir(out_dir, function() + return Result.try(function(try) + try(std.download_file(download.download_url, out_file)) + try(std.unpack(out_file)) + end) + end)) + end + end) +end + +---@class BuildInstruction +---@field target? Platform | Platform[] +---@field run string +---@field staged? boolean +---@field env? table<string, string> + +---@async +---@param build BuildInstruction +---@return Result +---@nodiscard +function M.run_build_instruction(build) + log.fmt_debug("build: run %s", build) + local ctx = installer.context() + if build.staged == false then + ctx:promote_cwd() + end + return platform.when { + unix = function() + return ctx.spawn.bash { + on_spawn = a.scope(function(_, stdio) + local stdin = stdio[1] + async_uv.write(stdin, "set -euxo pipefail;\n") + async_uv.write(stdin, build.run) + async_uv.shutdown(stdin) + async_uv.close(stdin) + end), + env = build.env, + } + end, + win = function() + return powershell.command(build.run, { + env = build.env, + }, ctx.spawn) + end, + } +end + +return M diff --git a/lua/mason-core/installer/registry/init.lua b/lua/mason-core/installer/registry/init.lua index 0cec2161..e97a8430 100644 --- a/lua/mason-core/installer/registry/init.lua +++ b/lua/mason-core/installer/registry/init.lua @@ -33,6 +33,7 @@ M.register_provider("luarocks", _.lazy_require "mason-core.installer.registry.pr M.register_provider("npm", _.lazy_require "mason-core.installer.registry.providers.npm") M.register_provider("nuget", _.lazy_require "mason-core.installer.registry.providers.nuget") M.register_provider("opam", _.lazy_require "mason-core.installer.registry.providers.opam") +M.register_provider("openvsx", _.lazy_require "mason-core.installer.registry.providers.openvsx") M.register_provider("pypi", _.lazy_require "mason-core.installer.registry.providers.pypi") ---@param purl Purl diff --git a/lua/mason-core/installer/registry/providers/generic/build.lua b/lua/mason-core/installer/registry/providers/generic/build.lua index 6d2769e1..a0d517d8 100644 --- a/lua/mason-core/installer/registry/providers/generic/build.lua +++ b/lua/mason-core/installer/registry/providers/generic/build.lua @@ -1,6 +1,6 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" -local build = require "mason-core.installer.managers.build" +local common = require "mason-core.installer.managers.common" local expr = require "mason-core.installer.registry.expr" local util = require "mason-core.installer.registry.util" @@ -15,7 +15,7 @@ local M = {} function M.parse(source, purl, opts) return Result.try(function(try) ---@type BuildInstruction - local build_instruction = try(util.coalesce_by_target(source.build, opts):ok_or "PLATFORM_UNSUPPORTED") + local build_instruction = try(util.coalesce_by_target(source.build, opts)) if build_instruction.env then local expr_ctx = { version = purl.version, target = build_instruction.target } @@ -34,7 +34,7 @@ end ---@param ctx InstallContext ---@param source ParsedGenericBuildSource function M.install(ctx, source) - return build.run(source.build) + return common.run_build_instruction(source.build) end return M diff --git a/lua/mason-core/installer/registry/providers/generic/download.lua b/lua/mason-core/installer/registry/providers/generic/download.lua index a7e4d046..4622a844 100644 --- a/lua/mason-core/installer/registry/providers/generic/download.lua +++ b/lua/mason-core/installer/registry/providers/generic/download.lua @@ -1,5 +1,6 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" +local common = require "mason-core.installer.managers.common" local expr = require "mason-core.installer.registry.expr" local util = require "mason-core.installer.registry.util" @@ -17,15 +18,25 @@ local M = {} ---@param opts PackageInstallOpts function M.parse(source, purl, opts) return Result.try(function(try) - local download = try(util.coalesce_by_target(source.download, opts):ok_or "PLATFORM_UNSUPPORTED") + local download = try(util.coalesce_by_target(source.download, opts)) local expr_ctx = { version = purl.version } ---@type { files: table<string, string> } local interpolated_download = try(expr.tbl_interpolate(download, expr_ctx)) + ---@type DownloadItem[] + local downloads = _.map(function(pair) + ---@type DownloadItem + return { + out_file = pair[1], + download_url = pair[2], + } + end, _.to_pairs(interpolated_download.files)) + ---@class ParsedGenericDownloadSource : ParsedPackageSource local parsed_source = { download = interpolated_download, + downloads = downloads, } return parsed_source end) @@ -35,13 +46,7 @@ end ---@param ctx InstallContext ---@param source ParsedGenericDownloadSource function M.install(ctx, source) - local std = require "mason-core.installer.managers.std" - return Result.try(function(try) - for out_file, url in pairs(source.download.files) do - try(std.download_file(url, out_file)) - try(std.unpack(out_file)) - end - end) + return common.download_files(ctx, source.downloads) end return M diff --git a/lua/mason-core/installer/registry/providers/github/build.lua b/lua/mason-core/installer/registry/providers/github/build.lua index bda0b655..1c17bb1a 100644 --- a/lua/mason-core/installer/registry/providers/github/build.lua +++ b/lua/mason-core/installer/registry/providers/github/build.lua @@ -1,6 +1,6 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" -local build = require "mason-core.installer.managers.build" +local common = require "mason-core.installer.managers.common" local expr = require "mason-core.installer.registry.expr" local util = require "mason-core.installer.registry.util" @@ -15,7 +15,7 @@ local M = {} function M.parse(source, purl, opts) return Result.try(function(try) ---@type BuildInstruction - local build_instruction = try(util.coalesce_by_target(source.build, opts):ok_or "PLATFORM_UNSUPPORTED") + local build_instruction = try(util.coalesce_by_target(source.build, opts)) local expr_ctx = { version = purl.version } @@ -44,7 +44,7 @@ function M.install(ctx, source) local std = require "mason-core.installer.managers.std" return Result.try(function(try) try(std.clone(source.repo, { rev = source.rev })) - try(build.run(source.build)) + try(common.run_build_instruction(source.build)) end) end diff --git a/lua/mason-core/installer/registry/providers/github/release.lua b/lua/mason-core/installer/registry/providers/github/release.lua index 5fe95cab..8c8a8a8f 100644 --- a/lua/mason-core/installer/registry/providers/github/release.lua +++ b/lua/mason-core/installer/registry/providers/github/release.lua @@ -1,17 +1,13 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" -local a = require "mason-core.async" +local common = require "mason-core.installer.managers.common" local expr = require "mason-core.installer.registry.expr" local providers = require "mason-core.providers" local settings = require "mason.settings" local util = require "mason-core.installer.registry.util" ----@class GitHubReleaseAsset ----@field target? Platform | Platform[] ----@field file string | string[] - ---@class GitHubReleaseSource : RegistryPackageSource ----@field asset GitHubReleaseAsset | GitHubReleaseAsset[] +---@field asset FileDownloadSpec | FileDownloadSpec[] local M = {} @@ -20,63 +16,22 @@ local M = {} ---@param opts PackageInstallOpts function M.parse(source, purl, opts) return Result.try(function(try) - local asset = try(util.coalesce_by_target(source.asset, opts):ok_or "PLATFORM_UNSUPPORTED") - local expr_ctx = { version = purl.version } + ---@type FileDownloadSpec + local asset = try(util.coalesce_by_target(try(expr.tbl_interpolate(source.asset, expr_ctx)), opts)) - ---@type { out_file: string, download_url: string }[] - local downloads = {} - - local interpolated_asset = try(expr.tbl_interpolate(asset, expr_ctx)) - - ---@param file string - ---@return Result # Result<{ source_file: string, out_file: string }> - local function parse_asset_file(file) - return Result.try(function(try) - local asset_file_components = _.split(":", file) - local source_file = try(expr.interpolate(_.head(asset_file_components), expr_ctx)) - local out_file = try(expr.interpolate(_.last(asset_file_components), expr_ctx)) - - if _.matches("/$", out_file) then - -- out_file is a dir expression (e.g. "libexec/") - out_file = out_file .. source_file - end - - return { - source_file = source_file, - out_file = out_file, - } - end) - end - - local get_downloads = _.map(function(asset_file) - return { - out_file = asset_file.out_file, - download_url = settings.current.github.download_url_template:format( - ("%s/%s"):format(purl.namespace, purl.name), - purl.version, - asset_file.source_file - ), - } + local downloads = common.parse_downloads(asset, function(file) + return settings.current.github.download_url_template:format( + ("%s/%s"):format(purl.namespace, purl.name), + purl.version, + file + ) end) - if type(asset.file) == "string" then - local parsed_asset_file = try(parse_asset_file(asset.file)) - downloads = get_downloads { parsed_asset_file } - interpolated_asset.file = parsed_asset_file.out_file - else - local parsed_asset_files = {} - for _, file in ipairs(asset.file) do - table.insert(parsed_asset_files, try(parse_asset_file(file))) - end - downloads = get_downloads(parsed_asset_files) - interpolated_asset.file = _.map(_.prop "out_file", parsed_asset_files) - end - ---@class ParsedGitHubReleaseSource : ParsedPackageSource local parsed_source = { repo = ("%s/%s"):format(purl.namespace, purl.name), - asset = interpolated_asset, + asset = common.normalize_files(asset), downloads = downloads, } return parsed_source @@ -87,26 +42,7 @@ end ---@param ctx InstallContext ---@param source ParsedGitHubReleaseSource function M.install(ctx, source) - local std = require "mason-core.installer.managers.std" - - return Result.try(function(try) - for __, download in ipairs(source.downloads) do - a.scheduler() - local out_dir = vim.fn.fnamemodify(download.out_file, ":h") - local out_file = vim.fn.fnamemodify(download.out_file, ":t") - if out_dir ~= "." then - try(Result.pcall(function() - ctx.fs:mkdirp(out_dir) - end)) - end - try(ctx:chdir(out_dir, function() - return Result.try(function(try) - try(std.download_file(download.download_url, out_file)) - try(std.unpack(out_file)) - end) - end)) - end - end) + return common.download_files(ctx, source.downloads) end ---@async diff --git a/lua/mason-core/installer/registry/providers/npm.lua b/lua/mason-core/installer/registry/providers/npm.lua index d1865b96..e8489fe8 100644 --- a/lua/mason-core/installer/registry/providers/npm.lua +++ b/lua/mason-core/installer/registry/providers/npm.lua @@ -1,7 +1,6 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local providers = require "mason-core.providers" -local util = require "mason-core.installer.registry.util" ---@param purl Purl local function purl_to_npm(purl) diff --git a/lua/mason-core/installer/registry/providers/openvsx.lua b/lua/mason-core/installer/registry/providers/openvsx.lua new file mode 100644 index 00000000..df52807a --- /dev/null +++ b/lua/mason-core/installer/registry/providers/openvsx.lua @@ -0,0 +1,63 @@ +local Result = require "mason-core.result" +local common = require "mason-core.installer.managers.common" +local expr = require "mason-core.installer.registry.expr" +local providers = require "mason-core.providers" +local util = require "mason-core.installer.registry.util" + +local M = {} + +---@class OpenVSXSourceDownload : FileDownloadSpec +---@field target_platform? string + +---@class OpenVSXSource : RegistryPackageSource +---@field download FileDownloadSpec | FileDownloadSpec[] + +---@param source OpenVSXSource +---@param purl Purl +---@param opts PackageInstallOpts +function M.parse(source, purl, opts) + return Result.try(function(try) + local expr_ctx = { version = purl.version } + ---@type OpenVSXSourceDownload + local download = try(util.coalesce_by_target(try(expr.tbl_interpolate(source.download, expr_ctx)), opts)) + + local downloads = common.parse_downloads(download, function(file) + if download.target_platform then + return ("https://open-vsx.org/api/%s/%s/%s/%s/file/%s"):format( + purl.namespace, + purl.name, + download.target_platform, + purl.version, + file + ) + else + return ("https://open-vsx.org/api/%s/%s/%s/file/%s"):format( + purl.namespace, + purl.name, + purl.version, + file + ) + end + end) + + ---@class ParsedOpenVSXSource : ParsedPackageSource + local parsed_source = { + download = common.normalize_files(download), + downloads = downloads, + } + return parsed_source + end) +end + +---@param ctx InstallContext +---@param source ParsedOpenVSXSource +function M.install(ctx, source) + return common.download_files(ctx, source.downloads) +end + +---@param purl Purl +function M.get_versions(purl) + return providers.openvsx.get_all_versions(purl.namespace, purl.name) +end + +return M diff --git a/lua/mason-core/installer/registry/util.lua b/lua/mason-core/installer/registry/util.lua index 33aa5c33..d0045a3e 100644 --- a/lua/mason-core/installer/registry/util.lua +++ b/lua/mason-core/installer/registry/util.lua @@ -10,10 +10,10 @@ local M = {} ---@generic T : { target: Platform | Platform[] } ---@param candidates T[] | T ---@param opts PackageInstallOpts ----@return Optional # Optional<T> +---@return Result # Result<T> function M.coalesce_by_target(candidates, opts) if not vim.tbl_islist(candidates) then - return Optional.of(candidates) + return Result.success(candidates) end return Optional.of_nilable(_.find_first(function(asset) if opts.target then @@ -33,7 +33,7 @@ function M.coalesce_by_target(candidates, opts) return platform.is[asset.target] end end - end, candidates)) + end, candidates)):ok_or "PLATFORM_UNSUPPORTED" end ---Checks whether a custom version of a package installation corresponds to a valid version. diff --git a/lua/mason-core/providers/init.lua b/lua/mason-core/providers/init.lua index 15e8081c..a97d1b60 100644 --- a/lua/mason-core/providers/init.lua +++ b/lua/mason-core/providers/init.lua @@ -44,6 +44,10 @@ local settings = require "mason.settings" ---@class GolangProvider ---@field get_all_versions? async fun(pkg: string): Result # Result<string[]> +---@class OpenVSXProvider +---@field get_latest_version? async fun(namespace: string, extension: string): Result # Result<Crate> +---@field get_all_versions? async fun(namespace: string, extension: string): Result # Result<string[]> + ---@class Provider ---@field github? GitHubProvider ---@field npm? NpmProvider @@ -52,6 +56,7 @@ local settings = require "mason.settings" ---@field packagist? PackagistProvider ---@field crates? CratesProvider ---@field golang? GolangProvider +---@field openvsx? OpenVSXProvider local function service_mt(service) return setmetatable({}, { diff --git a/lua/mason-core/semver.lua b/lua/mason-core/semver.lua index 635b7e36..4e45567d 100644 --- a/lua/mason-core/semver.lua +++ b/lua/mason-core/semver.lua @@ -4,12 +4,14 @@ local semver = require "mason-vendor.semver" local M = {} ---@param version string +---@return Semver function M.new(version) version = version:gsub("^v", "") return semver(version) end ---@param version string +---@return Result # Result<Semver> function M.parse(version) return Result.pcall(M.new, version) end diff --git a/lua/mason-registry/api.lua b/lua/mason-registry/api.lua index 853690b9..b4acea63 100644 --- a/lua/mason-registry/api.lua +++ b/lua/mason-registry/api.lua @@ -120,4 +120,13 @@ api.golang = { }, } +api.openvsx = { + versions = { + ---@type ApiSignature<{ namespace: string, extension: string }> + latest = get "/api/openvsx/{namespace}/{extension}/versions/latest", + ---@type ApiSignature<{ namespace: string, extension: string }> + all = get "/api/openvsx/{namespace}/{extension}/versions/all", + }, +} + return api diff --git a/lua/mason/providers/client/init.lua b/lua/mason/providers/client/init.lua index 0bc264fc..8035b7eb 100644 --- a/lua/mason/providers/client/init.lua +++ b/lua/mason/providers/client/init.lua @@ -5,4 +5,5 @@ return { pypi = require "mason.providers.client.pypi", rubygems = require "mason.providers.client.rubygems", golang = require "mason.providers.client.golang", + openvsx = require "mason.providers.client.openvsx", } diff --git a/lua/mason/providers/client/openvsx.lua b/lua/mason/providers/client/openvsx.lua new file mode 100644 index 00000000..32428be3 --- /dev/null +++ b/lua/mason/providers/client/openvsx.lua @@ -0,0 +1,26 @@ +local _ = require "mason-core.functional" +local fetch = require "mason-core.fetch" +local semver = require "mason-core.semver" + +---@param path string +local function api_url(path) + return ("https://open-vsx.org/api/%s"):format(path) +end + +---@param version string +local function maybe_semver_sort(version) + return semver.parse(version):get_or_else(version) +end + +---@type OpenVSXProvider +return { + get_latest_version = function(namespace, extension) + return fetch(api_url("%s/%s"):format(namespace, extension)):map_catching(vim.json.decode):map(_.prop "version") + end, + get_all_versions = function(namespace, extension) + return fetch(api_url("%s/%s/versions"):format(namespace, extension)) + :map_catching(vim.json.decode) + :map(_.compose(_.keys, _.prop "versions")) + :map(_.compose(_.reverse, _.sort_by(maybe_semver_sort))) + end, +} diff --git a/lua/mason/providers/registry-api/init.lua b/lua/mason/providers/registry-api/init.lua index 0f98a407..d8802124 100644 --- a/lua/mason/providers/registry-api/init.lua +++ b/lua/mason/providers/registry-api/init.lua @@ -61,4 +61,12 @@ return { return api.golang.versions.all { pkg = api.encode_uri_component(pkg) } end, }, + openvsx = { + get_latest_version = function(namespace, extension) + return api.openvsx.versions.latest { namespace = namespace, extension = extension } + end, + get_all_versions = function(namespace, extension) + return api.openvsx.versions.all { namespace = namespace, extension = extension } + end, + }, } diff --git a/tests/mason-core/installer/managers/build_spec.lua b/tests/mason-core/installer/managers/build_spec.lua index 73dd63c1..e709fdd0 100644 --- a/tests/mason-core/installer/managers/build_spec.lua +++ b/tests/mason-core/installer/managers/build_spec.lua @@ -1,5 +1,5 @@ local Result = require "mason-core.result" -local build = require "mason-core.installer.managers.build" +local common = require "mason-core.installer.managers.common" local installer = require "mason-core.installer" local match = require "luassert.match" local mock = require "luassert.mock" @@ -25,7 +25,7 @@ describe("build manager", function() ) local result = installer.exec_in_context(ctx, function() - return build.run { + return common.run_build_instruction { run = [[npm install && npm run compile]], env = { MASON_VERSION = "2023-03-09", @@ -57,7 +57,7 @@ describe("build manager", function() stub(ctx.spawn, "bash", mockx.returns(Result.success())) local result = installer.exec_in_context(ctx, function() - return build.run { + return common.run_build_instruction { run = "make", staged = false, } diff --git a/tests/mason-core/installer/managers/common_spec.lua b/tests/mason-core/installer/managers/common_spec.lua new file mode 100644 index 00000000..e72d7697 --- /dev/null +++ b/tests/mason-core/installer/managers/common_spec.lua @@ -0,0 +1,161 @@ +local Result = require "mason-core.result" +local _ = require "mason-core.functional" +local common = require "mason-core.installer.managers.common" +local installer = require "mason-core.installer" +local match = require "luassert.match" +local mock = require "luassert.mock" +local spy = require "luassert.spy" +local std = require "mason-core.installer.managers.std" +local stub = require "luassert.stub" + +describe("common manager :: download", function() + it("should parse download files from common structure", function() + local url_generator = _.format "https://example.com/%s" + + assert.same( + { + { + download_url = "https://example.com/abc.jar", + out_file = "abc.jar", + }, + }, + common.parse_downloads({ + file = "abc.jar", + }, url_generator) + ) + + assert.same( + { + { + download_url = "https://example.com/abc.jar", + out_file = "lib/abc.jar", + }, + }, + common.parse_downloads({ + file = "abc.jar:lib/", + }, url_generator) + ) + + assert.same( + { + { + download_url = "https://example.com/abc.jar", + out_file = "lib/abc.jar", + }, + { + download_url = "https://example.com/file.jar", + out_file = "lib/nested/new-name.jar", + }, + }, + common.parse_downloads({ + file = { "abc.jar:lib/", "file.jar:lib/nested/new-name.jar" }, + }, url_generator) + ) + end) + + it("should download files", function() + local ctx = create_dummy_context() + stub(std, "download_file", mockx.returns(Result.success())) + stub(std, "unpack", mockx.returns(Result.success())) + + local result = installer.exec_in_context(ctx, function() + return common.download_files(ctx, { + { out_file = "file.jar", download_url = "https://example.com/file.jar" }, + { out_file = "LICENSE.md", download_url = "https://example.com/LICENSE" }, + }) + end) + + assert.is_true(result:is_success()) + assert.spy(std.download_file).was_called(2) + assert.spy(std.download_file).was_called_with("https://example.com/file.jar", "file.jar") + assert.spy(std.download_file).was_called_with("https://example.com/LICENSE", "LICENSE.md") + assert.spy(std.unpack).was_called(2) + assert.spy(std.unpack).was_called_with "file.jar" + assert.spy(std.unpack).was_called_with "LICENSE.md" + end) + + it("should download files to specified directory", function() + local ctx = create_dummy_context() + stub(std, "download_file", mockx.returns(Result.success())) + stub(std, "unpack", mockx.returns(Result.success())) + stub(ctx.fs, "mkdirp") + + local result = installer.exec_in_context(ctx, function() + return common.download_files(ctx, { + { out_file = "lib/file.jar", download_url = "https://example.com/file.jar" }, + { out_file = "doc/LICENSE.md", download_url = "https://example.com/LICENSE" }, + { out_file = "nested/path/to/file", download_url = "https://example.com/some-file" }, + }) + end) + + assert.is_true(result:is_success()) + + assert.spy(ctx.fs.mkdirp).was_called(3) + assert.spy(ctx.fs.mkdirp).was_called_with(match.is_ref(ctx.fs), "lib") + assert.spy(ctx.fs.mkdirp).was_called_with(match.is_ref(ctx.fs), "doc") + assert.spy(ctx.fs.mkdirp).was_called_with(match.is_ref(ctx.fs), "nested/path/to") + end) +end) + +describe("common manager :: build", function() + it("should run build instruction", function() + local ctx = create_dummy_context() + local uv = require "mason-core.async.uv" + spy.on(ctx, "promote_cwd") + stub(uv, "write") + stub(uv, "shutdown") + stub(uv, "close") + local stdin = mock.new() + stub( + ctx.spawn, + "bash", ---@param args SpawnArgs + function(args) + args.on_spawn(mock.new(), { stdin }) + return Result.success() + end + ) + + local result = installer.exec_in_context(ctx, function() + return common.run_build_instruction { + run = [[npm install && npm run compile]], + env = { + MASON_VERSION = "2023-03-09", + SOME_VALUE = "here", + }, + } + end) + + assert.is_true(result:is_success()) + assert.spy(ctx.promote_cwd).was_called(0) + assert.spy(ctx.spawn.bash).was_called(1) + assert.spy(ctx.spawn.bash).was_called_with(match.tbl_containing { + on_spawn = match.is_function(), + env = match.same { + MASON_VERSION = "2023-03-09", + SOME_VALUE = "here", + }, + }) + assert.spy(uv.write).was_called(2) + assert.spy(uv.write).was_called_with(stdin, "set -euxo pipefail;\n") + assert.spy(uv.write).was_called_with(stdin, "npm install && npm run compile") + assert.spy(uv.shutdown).was_called_with(stdin) + assert.spy(uv.close).was_called_with(stdin) + end) + + it("should promote cwd if not staged", function() + local ctx = create_dummy_context() + stub(ctx, "promote_cwd") + stub(ctx.spawn, "bash", mockx.returns(Result.success())) + + local result = installer.exec_in_context(ctx, function() + return common.run_build_instruction { + run = "make", + staged = false, + } + end) + + assert.is_true(result:is_success()) + assert.spy(ctx.promote_cwd).was_called(1) + assert.spy(ctx.spawn.bash).was_called(1) + end) +end) diff --git a/tests/mason-core/installer/registry/providers/generic/build_spec.lua b/tests/mason-core/installer/registry/providers/generic/build_spec.lua index ccc77ac3..443cb99a 100644 --- a/tests/mason-core/installer/registry/providers/generic/build_spec.lua +++ b/tests/mason-core/installer/registry/providers/generic/build_spec.lua @@ -121,8 +121,8 @@ end) describe("generic provider :: build :: installing", function() it("should install", function() local ctx = create_dummy_context() - local build = require "mason-core.installer.managers.build" - stub(build, "run", mockx.returns(Result.success())) + local common = require "mason-core.installer.managers.common" + stub(common, "run_build_instruction", mockx.returns(Result.success())) local result = installer.exec_in_context(ctx, function() return generic.install(ctx, { @@ -134,8 +134,8 @@ describe("generic provider :: build :: installing", function() end) assert.is_true(result:is_success()) - assert.spy(build.run).was_called(1) - assert.spy(build.run).was_called_with { + assert.spy(common.run_build_instruction).was_called(1) + assert.spy(common.run_build_instruction).was_called_with { run = "make", env = { VALUE = "here" }, } diff --git a/tests/mason-core/installer/registry/providers/generic/download_spec.lua b/tests/mason-core/installer/registry/providers/generic/download_spec.lua index 1d3583f9..4bcb1976 100644 --- a/tests/mason-core/installer/registry/providers/generic/download_spec.lua +++ b/tests/mason-core/installer/registry/providers/generic/download_spec.lua @@ -2,6 +2,7 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" local generic = require "mason-core.installer.registry.providers.generic" local installer = require "mason-core.installer" +local match = require "luassert.match" local stub = require "luassert.stub" ---@param overrides Purl @@ -17,6 +18,12 @@ describe("generic provider :: download :: parsing", function() it("should parse single download target", function() assert.same( Result.success { + downloads = { + { + out_file = "name.tar.gz", + download_url = "https://getpackage.org/downloads/1.2.0/name.tar.gz", + }, + }, download = { files = { ["name.tar.gz"] = [[https://getpackage.org/downloads/1.2.0/name.tar.gz]], @@ -36,6 +43,12 @@ describe("generic provider :: download :: parsing", function() it("should coalesce download target", function() assert.same( Result.success { + downloads = { + { + out_file = "name.tar.gz", + download_url = "https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz", + }, + }, download = { target = "linux_arm64", files = { @@ -88,31 +101,33 @@ end) describe("generic provider :: download :: installing", function() it("should install generic packages", function() local ctx = create_dummy_context() - local std = require "mason-core.installer.managers.std" - stub(std, "download_file", mockx.returns(Result.success())) - stub(std, "unpack", mockx.returns(Result.success())) + local common = require "mason-core.installer.managers.common" + stub(common, "download_files", mockx.returns(Result.success())) local result = installer.exec_in_context(ctx, function() return generic.install(ctx, { + downloads = { + { + out_file = "name.tar.gz", + download_url = "https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz", + }, + }, download = { + target = "linux_arm64", files = { ["name.tar.gz"] = [[https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz]], - ["archive.tar.gz"] = [[https://getpackage.org/downloads/linux-aarch64/1.2.0/archive.tar.gz]], }, }, }) end) assert.is_true(result:is_success()) - assert.spy(std.download_file).was_called(2) - assert - .spy(std.download_file) - .was_called_with("https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz", "name.tar.gz") - assert - .spy(std.download_file) - .was_called_with("https://getpackage.org/downloads/linux-aarch64/1.2.0/archive.tar.gz", "archive.tar.gz") - assert.spy(std.unpack).was_called(2) - assert.spy(std.unpack).was_called_with "name.tar.gz" - assert.spy(std.unpack).was_called_with "archive.tar.gz" + assert.spy(common.download_files).was_called(1) + assert.spy(common.download_files).was_called_with(match.is_ref(ctx), { + { + out_file = "name.tar.gz", + download_url = "https://getpackage.org/downloads/linux-aarch64/1.2.0/name.tar.gz", + }, + }) end) end) diff --git a/tests/mason-core/installer/registry/providers/github/build_spec.lua b/tests/mason-core/installer/registry/providers/github/build_spec.lua index b25c26d8..17667d2c 100644 --- a/tests/mason-core/installer/registry/providers/github/build_spec.lua +++ b/tests/mason-core/installer/registry/providers/github/build_spec.lua @@ -1,10 +1,6 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" local github = require "mason-core.installer.registry.providers.github" -local installer = require "mason-core.installer" -local match = require "luassert.match" -local mock = require "luassert.mock" -local stub = require "luassert.stub" ---@param overrides Purl local function purl(overrides) @@ -60,39 +56,3 @@ describe("github provider :: build :: parsing", function() ) end) end) - -describe("github provider :: build :: installing", function() - it("should install github build sources", function() - local ctx = create_dummy_context() - local std = require "mason-core.installer.managers.std" - local build = require "mason-core.installer.managers.build" - stub(std, "clone", mockx.returns(Result.success())) - stub(build, "run", mockx.returns(Result.success())) - - local result = installer.exec_in_context(ctx, function() - return github.install(ctx, { - repo = "namespace/name", - rev = "2023-03-09", - build = { - run = [[npm install && npm run compile]], - env = { - MASON_VERSION = "2023-03-09", - SOME_VALUE = "here", - }, - }, - }, purl()) - end) - - assert.is_true(result:is_success()) - assert.spy(std.clone).was_called(1) - assert.spy(std.clone).was_called_with("namespace/name", { rev = "2023-03-09" }) - assert.spy(build.run).was_called(1) - assert.spy(build.run).was_called_with { - run = [[npm install && npm run compile]], - env = { - MASON_VERSION = "2023-03-09", - SOME_VALUE = "here", - }, - } - end) -end) diff --git a/tests/mason-core/installer/registry/providers/github/release_spec.lua b/tests/mason-core/installer/registry/providers/github/release_spec.lua index 909eb36f..a6648b33 100644 --- a/tests/mason-core/installer/registry/providers/github/release_spec.lua +++ b/tests/mason-core/installer/registry/providers/github/release_spec.lua @@ -1,10 +1,10 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" +local common = require "mason-core.installer.managers.common" local github = require "mason-core.installer.registry.providers.github" local installer = require "mason-core.installer" local match = require "luassert.match" local registry_installer = require "mason-core.installer.registry" -local spy = require "luassert.spy" local stub = require "luassert.stub" ---@param overrides Purl @@ -285,6 +285,7 @@ describe("github provider :: release :: installing", function() local std = require "mason-core.installer.managers.std" stub(std, "download_file", mockx.returns(Result.success())) stub(std, "unpack", mockx.returns(Result.success())) + stub(common, "download_files", mockx.returns(Result.success())) local result = installer.exec_in_context(ctx, function() return github.install(ctx, { @@ -306,62 +307,16 @@ describe("github provider :: release :: installing", function() end) assert.is_true(result:is_success()) - assert.spy(std.download_file).was_called(2) - assert.spy(std.download_file).was_called_with( - "https://github.com/namespace/name/releases/download/2023-03-09/file-linux-amd64-2023-03-09.tar.gz", - "file-linux-amd64-2023-03-09.tar.gz" - ) - assert.spy(std.download_file).was_called_with( - "https://github.com/namespace/name/releases/download/2023-03-09/another-file-linux-amd64-2023-03-09.tar.gz", - "another-file-linux-amd64-2023-03-09.tar.gz" - ) - assert.spy(std.unpack).was_called(2) - assert.spy(std.unpack).was_called_with "file-linux-amd64-2023-03-09.tar.gz" - assert.spy(std.unpack).was_called_with "another-file-linux-amd64-2023-03-09.tar.gz" - end) - - it("should install github release assets into specified output directory", function() - local ctx = create_dummy_context() - local std = require "mason-core.installer.managers.std" - local download_file_cwd, unpack_cwd - stub(std, "download_file", function() - download_file_cwd = ctx.cwd:get() - return Result.success() - end) - stub(std, "unpack", function() - unpack_cwd = ctx.cwd:get() - return Result.success() - end) - stub(ctx.fs, "mkdirp") - spy.on(ctx, "chdir") - - local result = installer.exec_in_context(ctx, function() - return github.install(ctx, { - repo = "namespace/name", - asset = { - file = "file.zip", - }, - downloads = { - { - out_file = "out/dir/file.zip", - download_url = "https://github.com/namespace/name/releases/download/2023-03-09/file.zip", - }, - }, - }) - end) - - assert.is_true(result:is_success()) - assert.spy(ctx.fs.mkdirp).was_called(1) - assert.spy(ctx.fs.mkdirp).was_called_with(match.is_ref(ctx.fs), "out/dir") - assert.spy(ctx.chdir).was_called(1) - assert.spy(ctx.chdir).was_called_with(match.is_ref(ctx), "out/dir", match.is_function()) - assert.spy(std.download_file).was_called(1) - assert.is_true(match.matches "out/dir$"(download_file_cwd)) - assert - .spy(std.download_file) - .was_called_with("https://github.com/namespace/name/releases/download/2023-03-09/file.zip", "file.zip") - assert.spy(std.unpack).was_called(1) - assert.is_true(match.matches "out/dir$"(unpack_cwd)) - assert.spy(std.unpack).was_called_with "file.zip" + assert.spy(common.download_files).was_called(1) + assert.spy(common.download_files).was_called_with(match.is_ref(ctx), { + { + out_file = "file-linux-amd64-2023-03-09.tar.gz", + download_url = "https://github.com/namespace/name/releases/download/2023-03-09/file-linux-amd64-2023-03-09.tar.gz", + }, + { + out_file = "another-file-linux-amd64-2023-03-09.tar.gz", + download_url = "https://github.com/namespace/name/releases/download/2023-03-09/another-file-linux-amd64-2023-03-09.tar.gz", + }, + }) end) end) diff --git a/tests/mason-core/installer/registry/providers/openvsx_spec.lua b/tests/mason-core/installer/registry/providers/openvsx_spec.lua new file mode 100644 index 00000000..1452ea0f --- /dev/null +++ b/tests/mason-core/installer/registry/providers/openvsx_spec.lua @@ -0,0 +1,149 @@ +local Purl = require "mason-core.purl" +local Result = require "mason-core.result" +local common = require "mason-core.installer.managers.common" +local installer = require "mason-core.installer" +local match = require "luassert.match" +local openvsx = require "mason-core.installer.registry.providers.openvsx" +local stub = require "luassert.stub" + +---@param overrides Purl +local function purl(overrides) + local purl = Purl.parse("pkg:openvsx/namespace/name@1.10.1"):get_or_throw() + if not overrides then + return purl + end + return vim.tbl_deep_extend("force", purl, overrides) +end + +describe("openvsx provider :: download :: parsing", function() + it("should parse download source", function() + assert.same( + Result.success { + download = { + file = "file-1.10.1.jar", + }, + downloads = { + { + out_file = "file-1.10.1.jar", + download_url = "https://open-vsx.org/api/namespace/name/1.10.1/file/file-1.10.1.jar", + }, + }, + }, + openvsx.parse({ + download = { + file = "file-{{version}}.jar", + }, + }, purl()) + ) + end) + + it("should parse download source with multiple targets", function() + assert.same( + Result.success { + download = { + target = "linux_x64", + file = "file-linux-amd64-1.0.0.vsix", + }, + downloads = { + { + out_file = "file-linux-amd64-1.0.0.vsix", + download_url = "https://open-vsx.org/api/namespace/name/1.0.0/file/file-linux-amd64-1.0.0.vsix", + }, + }, + }, + openvsx.parse({ + download = { + { + target = "win_arm", + file = "file-win-arm-{{version}}.vsix", + }, + { + target = "linux_x64", + file = "file-linux-amd64-{{version}}.vsix", + }, + }, + }, purl { version = "1.0.0" }, { target = "linux_x64" }) + ) + end) + + it("should parse download source with output to different directory", function() + assert.same( + Result.success { + download = { + file = "out-dir/file-linux-amd64-1.10.1.vsix", + }, + downloads = { + { + out_file = "out-dir/file-linux-amd64-1.10.1.vsix", + download_url = "https://open-vsx.org/api/namespace/name/1.10.1/file/file-linux-amd64-1.10.1.vsix", + }, + }, + }, + openvsx.parse({ + download = { + file = "file-linux-amd64-{{version}}.vsix:out-dir/", + }, + }, purl(), { target = "linux_x64" }) + ) + end) + + it("should recognize target_platform when available", function() + assert.same( + Result.success { + download = { + file = "file-linux-1.10.1@win32-arm64.vsix", + target = "win_arm64", + target_platform = "win32-arm64", + }, + downloads = { + { + out_file = "file-linux-1.10.1@win32-arm64.vsix", + download_url = "https://open-vsx.org/api/namespace/name/win32-arm64/1.10.1/file/file-linux-1.10.1@win32-arm64.vsix", + }, + }, + }, + openvsx.parse({ + download = { + { + target = "win_arm64", + file = "file-linux-{{version}}@win32-arm64.vsix", + target_platform = "win32-arm64", + }, + }, + }, purl(), { target = "win_arm64" }) + ) + end) +end) + +describe("openvsx provider :: download :: installing", function() + it("should install openvsx assets", function() + local ctx = create_dummy_context() + local std = require "mason-core.installer.managers.std" + stub(std, "download_file", mockx.returns(Result.success())) + stub(std, "unpack", mockx.returns(Result.success())) + stub(common, "download_files", mockx.returns(Result.success())) + + local result = installer.exec_in_context(ctx, function() + return openvsx.install(ctx, { + download = { + file = "file-1.10.1.jar", + }, + downloads = { + { + out_file = "file-1.10.1.jar", + download_url = "https://open-vsx.org/api/namespace/name/1.10.1/file/file-1.10.1.jar", + }, + }, + }) + end) + + assert.is_true(result:is_success()) + assert.spy(common.download_files).was_called(1) + assert.spy(common.download_files).was_called_with(match.is_ref(ctx), { + { + out_file = "file-1.10.1.jar", + download_url = "https://open-vsx.org/api/namespace/name/1.10.1/file/file-1.10.1.jar", + }, + }) + end) +end) diff --git a/tests/mason-core/installer/registry/util_spec.lua b/tests/mason-core/installer/registry/util_spec.lua index 09918943..851164d0 100644 --- a/tests/mason-core/installer/registry/util_spec.lua +++ b/tests/mason-core/installer/registry/util_spec.lua @@ -7,7 +7,7 @@ local util = require "mason-core.installer.registry.util" describe("registry installer util", function() it("should coalesce single target", function() local source = { value = "here" } - local coalesced = util.coalesce_by_target(source, {}):get() + local coalesced = util.coalesce_by_target(source, {}):get_or_nil() assert.is_true(match.is_ref(source)(coalesced)) end) @@ -19,7 +19,7 @@ describe("registry installer util", function() value = "here", }, source, - }, { target = "VIC64" }):get() + }, { target = "VIC64" }):get_or_nil() assert.is_true(match.is_ref(source)(coalesced)) end) |
