aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-registry/sources/github.lua
diff options
context:
space:
mode:
authorWilliam Boman <william@redwill.se>2023-03-12 08:21:15 +0100
committerGitHub <noreply@github.com>2023-03-12 08:21:15 +0100
commita01d02ad7f680aec98a1e2ec35b04cedd307cfa8 (patch)
tree1a09e274a1f2a4da85b911abcbb182a211035501 /lua/mason-registry/sources/github.lua
parentfeat(golangci-lint): support linux_arm64 (#1089) (diff)
downloadmason-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/sources/github.lua')
-rw-r--r--lua/mason-registry/sources/github.lua195
1 files changed, 195 insertions, 0 deletions
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