diff options
| author | William Boman <william@redwill.se> | 2023-03-12 08:21:15 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-12 08:21:15 +0100 |
| commit | a01d02ad7f680aec98a1e2ec35b04cedd307cfa8 (patch) | |
| tree | 1a09e274a1f2a4da85b911abcbb182a211035501 /lua/mason-registry | |
| parent | feat(golangci-lint): support linux_arm64 (#1089) (diff) | |
| download | mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.gz mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.bz2 mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.lz mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.xz mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.zst mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.zip | |
feat: add github registry source capabilities (#1091)
Diffstat (limited to 'lua/mason-registry')
| -rw-r--r-- | lua/mason-registry/init.lua | 38 | ||||
| -rw-r--r-- | lua/mason-registry/sources/github.lua | 195 | ||||
| -rw-r--r-- | lua/mason-registry/sources/init.lua | 18 | ||||
| -rw-r--r-- | lua/mason-registry/sources/lua.lua | 6 |
4 files changed, 252 insertions, 5 deletions
diff --git a/lua/mason-registry/init.lua b/lua/mason-registry/init.lua index 01f18c90..a87b3b07 100644 --- a/lua/mason-registry/init.lua +++ b/lua/mason-registry/init.lua @@ -11,7 +11,7 @@ local sources = require "mason-registry.sources" ---@field get_all_package_names fun(self: RegistrySource): string[] ---@field get_display_name fun(self: RegistrySource): string ---@field is_installed fun(self: RegistrySource): boolean ----@field install async fun(self: RegistrySource): Result +---@field get_installer fun(self: RegistrySource): Optional # Optional<async fun (): Result> ---@class MasonRegistry : EventEmitter ---@diagnostic disable-next-line: assign-type-mismatch @@ -118,4 +118,40 @@ function M.get_all_packages() return get_packages(M.get_all_package_names()) end +---@param cb fun(success: boolean, err: any?) +function M.update(cb) + local a = require "mason-core.async" + local Result = require "mason-core.result" + + a.run(function() + return Result.try(function(try) + local updated_sources = {} + for source in sources.iter { include_uninstalled = true } do + source:get_installer():if_present(function(installer) + try(installer():map_err(function(err) + return ("%s failed to install: %s"):format(source, err) + end)) + table.insert(updated_sources, source) + end) + end + return updated_sources + end) + end, function(success, sources_or_err) + if not success then + cb(success, sources_or_err) + return + end + sources_or_err + :on_success(function(updated_sources) + if #updated_sources > 0 then + M:emit("update", updated_sources) + end + cb(true, updated_sources) + end) + :on_failure(function(err) + cb(false, err) + end) + end) +end + return M diff --git a/lua/mason-registry/sources/github.lua b/lua/mason-registry/sources/github.lua new file mode 100644 index 00000000..0cddff21 --- /dev/null +++ b/lua/mason-registry/sources/github.lua @@ -0,0 +1,195 @@ +local log = require "mason-core.log" +local fs = require "mason-core.fs" +local providers = require "mason-core.providers" +local _ = require "mason-core.functional" +local path = require "mason-core.path" +local Result = require "mason-core.result" +local Optional = require "mason-core.optional" +local fetch = require "mason-core.fetch" +local settings = require "mason.settings" +local platform = require "mason-core.platform" +local spawn = require "mason-core.spawn" +local Pkg = require "mason-core.package" +local registry_installer = require "mason-core.installer.registry" + +-- Parse sha256sum text output to a table<filename: string, sha256sum: string> structure +local parse_checksums = _.compose(_.from_pairs, _.map(_.compose(_.reverse, _.split " ")), _.split "\n", _.trim) + +---@class GitHubRegistrySourceSpec +---@field id string +---@field repo string +---@field namespace string +---@field name string +---@field version string? + +---@class GitHubRegistrySource : RegistrySource +---@field spec GitHubRegistrySourceSpec +---@field repo string +---@field root_dir string +---@field private data_file string +---@field private info_file string +---@field buffer table<string, Package>? +local GitHubRegistrySource = {} +GitHubRegistrySource.__index = GitHubRegistrySource + +---@param spec GitHubRegistrySourceSpec +function GitHubRegistrySource.new(spec) + local root_dir = path.concat { path.registry_prefix(), "github", spec.namespace, spec.name } + return setmetatable({ + spec = spec, + root_dir = root_dir, + data_file = path.concat { root_dir, "registry.json" }, + info_file = path.concat { root_dir, "info.json" }, + }, GitHubRegistrySource) +end + +function GitHubRegistrySource:is_installed() + return fs.sync.file_exists(self.data_file) +end + +function GitHubRegistrySource:reload() + if not self:is_installed() then + return + end + local data = vim.json.decode(fs.sync.read_file(self.data_file)) + self.buffer = _.compose( + _.index_by(_.prop "name"), + _.filter_map( + ---@param spec RegistryPackageSpec + function(spec) + -- registry+v1 specifications doesn't include a schema property, so infer it + spec.schema = spec.schema or "registry+v1" + + if not registry_installer.SCHEMA_CAP[spec.schema] then + log.fmt_debug("Excluding package=%s with unsupported schema_version=%s", spec.name, spec.schema) + return Optional.empty() + end + + -- hydrate Pkg.Lang index + _.each(function(lang) + local _ = Pkg.Lang[lang] + end, spec.languages) + + local pkg = self.buffer and self.buffer[spec.name] + if pkg then + -- Apply spec to the existing Package instance. This is important as to not have lingering package + -- instances. + pkg.spec = spec + return Optional.of(pkg) + end + return Optional.of(Pkg.new(spec)) + end + ) + )(data) + return self.buffer +end + +function GitHubRegistrySource:get_buffer() + return self.buffer or self:reload() or {} +end + +---@param pkg string +---@return Package? +function GitHubRegistrySource:get_package(pkg) + return self:get_buffer()[pkg] +end + +function GitHubRegistrySource:get_all_package_names() + return _.keys(self:get_buffer()) +end + +function GitHubRegistrySource:get_installer() + return Optional.of(_.partial(self.install, self)) +end + +---@async +function GitHubRegistrySource:install() + return Result.try(function(try) + if not fs.async.dir_exists(self.root_dir) then + log.debug("Creating registry directory", self) + try(Result.pcall(fs.async.mkdirp, self.root_dir)) + end + + local version = self.spec.version + if version == nil or version == "latest" then + log.trace("Resolving latest version for registry", self) + ---@type GitHubRelease + local release = try(providers.github.get_latest_release(self.spec.repo)) + version = release.tag_name + log.trace("Resolved latest registry version", self, version) + end + + try(fetch(settings.current.github.download_url_template:format(self.spec.repo, version, "registry.json.zip"), { + out_file = path.concat { self.root_dir, "registry.json.zip" }, + }):map_err(_.always "Failed to download registry.json.zip.")) + + local checksums = try( + fetch(settings.current.github.download_url_template:format(self.spec.repo, version, "checksums.txt")):map_err( + _.always "Failed to download checksums.txt." + ) + ) + local parsed_checksums = parse_checksums(checksums) + + platform.when { + unix = function() + try(spawn.unzip({ "-o", "registry.json.zip", cwd = self.root_dir }):map_err(function(err) + return ("Failed to unpack registry contents: %s"):format(err.stderr) + end)) + end, + win = function() + local powershell = require "mason-core.managers.powershell" + powershell + .command( + ("Microsoft.PowerShell.Archive\\Expand-Archive -Force -Path %q -DestinationPath ."):format "registry.json.zip", + { + cwd = self.root_dir, + } + ) + :map_err(function(err) + return ("Failed to unpack registry contents: %s"):format(err.stderr) + end) + end, + } + pcall(fs.async.unlink, path.concat { self.root_dir, "registry.json.zip" }) + + try(Result.pcall( + fs.async.write_file, + self.info_file, + vim.json.encode { + checksums = parsed_checksums, + version = version, + download_timestamp = os.time(), + } + )) + end) + :on_success(function() + self:reload() + end) + :on_failure(function(err) + log.fmt_error("Failed to install registry %s. %s", self, err) + end) +end + +---@return { checksums: table<string, string>, version: string, download_timestamp: integer } +function GitHubRegistrySource:get_info() + return vim.json.decode(fs.sync.read_file(self.info_file)) +end + +function GitHubRegistrySource:get_display_name() + if self:is_installed() then + local info = self:get_info() + return ("github.com/%s version: %s"):format(self.spec.repo, info.version) + else + return ("github.com/%s [uninstalled]"):format(self.spec.repo) + end +end + +function GitHubRegistrySource:__tostring() + if self.spec.version then + return ("GitHubRegistrySource(repo=%s, version=%s)"):format(self.spec.repo, self.spec.version) + else + return ("GitHubRegistrySource(repo=%s)"):format(self.spec.repo) + end +end + +return GitHubRegistrySource diff --git a/lua/mason-registry/sources/init.lua b/lua/mason-registry/sources/init.lua index e4abe062..5a332326 100644 --- a/lua/mason-registry/sources/init.lua +++ b/lua/mason-registry/sources/init.lua @@ -4,7 +4,23 @@ local M = {} ---@return fun(): RegistrySource # Thunk to instantiate provider. local function parse(registry_id) local type, id = registry_id:match "^(.+):(.+)$" - if type == "lua" then + if type == "github" then + local namespace, name = id:match "^(.+)/(.+)$" + if not namespace or not name then + error(("Failed to parse repository from GitHub registry: %q."):format(registry_id), 0) + end + local name, version = unpack(vim.split(name, "@")) + return function() + local GitHubRegistrySource = require "mason-registry.sources.github" + return GitHubRegistrySource.new { + id = registry_id, + repo = ("%s/%s"):format(namespace, name), + namespace = namespace, + name = name, + version = version or "latest", + } + end + elseif type == "lua" then return function() local LuaRegistrySource = require "mason-registry.sources.lua" return LuaRegistrySource.new { diff --git a/lua/mason-registry/sources/lua.lua b/lua/mason-registry/sources/lua.lua index 7af4feee..ac41c03c 100644 --- a/lua/mason-registry/sources/lua.lua +++ b/lua/mason-registry/sources/lua.lua @@ -34,9 +34,9 @@ function LuaRegistrySource:is_installed() return ok end -function LuaRegistrySource:install() - local Result = require "mason-core.result" - return Result.success() +function LuaRegistrySource:get_installer() + local Optional = require "mason-core.optional" + return Optional.empty() end function LuaRegistrySource:get_display_name() |
