diff --git a/lua/mason-core/installer/compiler/compilers/nuget.lua b/lua/mason-core/installer/compiler/compilers/nuget.lua index 370c7b959..b95a0b09f 100644 --- a/lua/mason-core/installer/compiler/compilers/nuget.lua +++ b/lua/mason-core/installer/compiler/compilers/nuget.lua @@ -1,25 +1,83 @@ 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.compiler.expr" +local nuget = require "mason-core.installer.managers.nuget" +local util = require "mason-core.installer.compiler.util" local M = {} ----@param source RegistryPackageSource +---@class NugetPackageSource : RegistryPackageSource +---@field download FileDownloadSpec + +---@param source NugetPackageSource ---@param purl Purl function M.parse(source, purl) - ---@class ParsedNugetSource : ParsedPackageSource - local parsed_source = { - package = purl.name, - version = purl.version, - } + return Result.try(function(try) + local repository_url = _.path({ "qualifiers", "repository_url" }, purl) + + local download_item = nil + if source.download then + if not repository_url then + -- if not set we need to provide repository url because we need it for + -- download url discovery + repository_url = "https://api.nuget.org/v3/index.json" + end + + local index_file = try(nuget.fetch_nuget_endpoint(repository_url)) + + local resource = vim.iter(index_file.resources):find(function (v) + return v['@type'] == 'PackageBaseAddress/3.0.0' + end) + + assert(resource, "could not get PackageBaseAddress resource from nuget index file") + + local package_base_address = resource["@id"] + local package_lowercase = purl.name:lower() + + local nupkg_download_url = string.format( + "%s%s/%s/%s.%s.nupkg", + package_base_address, + package_lowercase, + purl.version, + package_lowercase, + purl.version + ) + + local expr_ctx = { version = purl.version } + + ---@type FileDownloadSpec + local download_spec = try(util.coalesce_by_target(try(expr.tbl_interpolate(source.download, expr_ctx)), {})) + + download_item = { + download_url = nupkg_download_url, + out_file = download_spec.file, + } + end + + ---@class ParsedNugetSource : ParsedPackageSource + ---@field download? DownloadItem + ---@field repository_url string Custom repository URL to pull from + local parsed_source = { + package = purl.name, + version = purl.version, + download = download_item, + repository_url = repository_url, + } - return Result.success(parsed_source) + return parsed_source + end) end ---@async ---@param ctx InstallContext ---@param source ParsedNugetSource function M.install(ctx, source) - local nuget = require "mason-core.installer.managers.nuget" - return nuget.install(source.package, source.version) + if source.download then + return common.download_files(ctx, { source.download }) + else + return nuget.install(source.package, source.version, source.repository_url) + end end ---@async diff --git a/lua/mason-core/installer/managers/nuget.lua b/lua/mason-core/installer/managers/nuget.lua index 5a4021d0e..ec83a7983 100644 --- a/lua/mason-core/installer/managers/nuget.lua +++ b/lua/mason-core/installer/managers/nuget.lua @@ -1,4 +1,5 @@ local Result = require "mason-core.result" +local fetch = require "mason-core.fetch" local installer = require "mason-core.installer" local log = require "mason-core.log" local platform = require "mason-core.platform" @@ -8,19 +9,41 @@ local M = {} ---@async ---@param package string ---@param version string +---@param repository_url string ---@nodiscard -function M.install(package, version) +function M.install(package, version, repository_url) log.fmt_debug("nuget: install %s %s", package, version) local ctx = installer.context() - ctx.stdio_sink:stdout(("Installing nuget package %s@%s…\n"):format(package, version)) - return ctx.spawn.dotnet { + ctx.stdio_sink.stdout(("Installing nuget package %s@%s…\n"):format(package, version)) + local args = { "tool", "update", "--tool-path", ".", { "--version", version }, - package, } + + if repository_url then + table.insert(args, { "--add-source", repository_url }) + end + + table.insert(args, package) + + return ctx.spawn.dotnet(args) +end + +---@alias NugetIndexResource { '@id': string, '@type': string} +---@alias NugetIndexFile { version: string, resources: NugetIndexResource[]} + +---@async +---@param repository_url string +---@return Result # Result +function M.fetch_nuget_endpoint(repository_url) + return fetch(repository_url, { + headers = { + Accept = "application/json", + }, + }):map_catching(vim.json.decode) end ---@param bin string diff --git a/lua/mason-core/installer/managers/std.lua b/lua/mason-core/installer/managers/std.lua index 701bb6c99..9f38b8cda 100644 --- a/lua/mason-core/installer/managers/std.lua +++ b/lua/mason-core/installer/managers/std.lua @@ -224,6 +224,7 @@ local unpack_by_filename = _.cond { { _.matches "%.tar%.zst$", untar_zst }, { _.matches "%.zip$", unzip }, { _.matches "%.vsix$", unzip }, + { _.matches "%.nupkg$", unzip }, { _.matches "%.gz$", gunzip }, { _.T, _.compose(Result.success, _.format "%q doesn't need unpacking.") }, } diff --git a/tests/fixtures/purl-test-suite-data.json b/tests/fixtures/purl-test-suite-data.json index 3856ab4ee..3cfb2e08d 100644 --- a/tests/fixtures/purl-test-suite-data.json +++ b/tests/fixtures/purl-test-suite-data.json @@ -157,13 +157,13 @@ }, { "description": "nuget names are case sensitive", - "purl": "pkg:Nuget/EnterpriseLibrary.Common@6.0.1304", - "canonical_purl": "pkg:nuget/EnterpriseLibrary.Common@6.0.1304", + "purl": "pkg:Nuget/EnterpriseLibrary.Common@6.0.1304?repository_url=https://api.nuget.org/v3/index.json", + "canonical_purl": "pkg:nuget/EnterpriseLibrary.Common@6.0.1304?repository_url=https://api.nuget.org/v3/index.json", "type": "nuget", "namespace": null, "name": "EnterpriseLibrary.Common", "version": "6.0.1304", - "qualifiers": null, + "qualifiers": { "repository_url": "https://api.nuget.org/v3/index.json" }, "subpath": null, "is_invalid": false }, diff --git a/tests/mason-core/installer/compiler/compilers/nuget_spec.lua b/tests/mason-core/installer/compiler/compilers/nuget_spec.lua index 973c0932e..3a1fc9d93 100644 --- a/tests/mason-core/installer/compiler/compilers/nuget_spec.lua +++ b/tests/mason-core/installer/compiler/compilers/nuget_spec.lua @@ -6,7 +6,7 @@ local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) - local purl = Purl.parse("pkg:nuget/package@2.2.0"):get_or_throw() + local purl = Purl.parse("pkg:nuget/package@2.2.0?repository_url=https://api.nuget.org/v3/index.json"):get_or_throw() if not overrides then return purl end @@ -19,6 +19,7 @@ describe("nuget compiler :: parsing", function() Result.success { package = "package", version = "2.2.0", + repository_url = "https://api.nuget.org/v3/index.json", }, nuget.parse({}, purl()) ) @@ -45,11 +46,12 @@ describe("nuget compiler :: installing", function() return nuget.install(ctx, { package = "package", version = "1.5.0", + repository_url = "https://api.nuget.org/v3/index.json", }) end) assert.is_true(result:is_success()) assert.spy(manager.install).was_called(1) - assert.spy(manager.install).was_called_with("package", "1.5.0") + assert.spy(manager.install).was_called_with("package", "1.5.0", "https://api.nuget.org/v3/index.json") end) end)