diff options
| -rw-r--r-- | lua/mason-core/installer/InstallHandle.lua | 2 | ||||
| -rw-r--r-- | lua/mason-core/installer/context/init.lua | 2 | ||||
| -rw-r--r-- | lua/mason-core/package/AbstractPackage.lua | 10 | ||||
| -rw-r--r-- | lua/mason-core/receipt.lua | 12 | ||||
| -rw-r--r-- | lua/mason-registry/init.lua | 6 | ||||
| -rw-r--r-- | lua/mason-registry/sources/github.lua | 1 | ||||
| -rw-r--r-- | lua/mason-registry/sources/init.lua | 17 | ||||
| -rw-r--r-- | lua/mason-registry/sources/synthesized.lua | 109 | ||||
| -rw-r--r-- | lua/mason/ui/instance.lua | 2 | ||||
| -rw-r--r-- | tests/mason-registry/sources/collection_spec.lua | 19 |
10 files changed, 164 insertions, 16 deletions
diff --git a/lua/mason-core/installer/InstallHandle.lua b/lua/mason-core/installer/InstallHandle.lua index d8b8941f..3846659e 100644 --- a/lua/mason-core/installer/InstallHandle.lua +++ b/lua/mason-core/installer/InstallHandle.lua @@ -43,7 +43,7 @@ function InstallHandleSpawnHandle:__tostring() end ---@class InstallHandle : EventEmitter ----@field package AbstractPackage +---@field public package AbstractPackage ---@field state InstallHandleState ---@field stdio_sink BufferedSink ---@field is_terminated boolean diff --git a/lua/mason-core/installer/context/init.lua b/lua/mason-core/installer/context/init.lua index 9af95f80..ae96f986 100644 --- a/lua/mason-core/installer/context/init.lua +++ b/lua/mason-core/installer/context/init.lua @@ -17,7 +17,7 @@ local receipt = require "mason-core.receipt" ---@field location InstallLocation ---@field spawn InstallContextSpawn ---@field handle InstallHandle ----@field package AbstractPackage +---@field public package AbstractPackage ---@field cwd InstallContextCwd ---@field opts PackageInstallOpts ---@field stdio_sink StdioSink diff --git a/lua/mason-core/package/AbstractPackage.lua b/lua/mason-core/package/AbstractPackage.lua index d0fde00d..5678f4dd 100644 --- a/lua/mason-core/package/AbstractPackage.lua +++ b/lua/mason-core/package/AbstractPackage.lua @@ -6,7 +6,6 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local fs = require "mason-core.fs" local log = require "mason-core.log" -local path = require "mason-core.path" local settings = require "mason.settings" local Semaphore = require("mason-core.async.control").Semaphore @@ -166,15 +165,10 @@ end ---@return string? function AbstractPackage:get_installed_version(location) return self:get_receipt(location) - :and_then( + :map( ---@param receipt InstallReceipt function(receipt) - local source = receipt:get_source() - if source.id then - return Purl.parse(source.id):map(_.prop "version"):ok() - else - return Optional.empty() - end + return receipt:get_installed_package_version() end ) :or_else(nil) diff --git a/lua/mason-core/receipt.lua b/lua/mason-core/receipt.lua index 42a7e882..bdf96254 100644 --- a/lua/mason-core/receipt.lua +++ b/lua/mason-core/receipt.lua @@ -1,3 +1,7 @@ +local Optional = require "mason-core.optional" +local Purl = require "mason-core.purl" +local _ = require "mason-core.functional" + local M = {} ---@alias InstallReceiptSchemaVersion @@ -41,6 +45,14 @@ function InstallReceipt:get_name() return self.name end +---@return string? +function InstallReceipt:get_installed_package_version() + local source = self:get_source() + if source.id then + return Purl.parse(source.id):map(_.prop "version"):get_or_nil() + end +end + function InstallReceipt:get_schema_version() return self.schema_version end diff --git a/lua/mason-registry/init.lua b/lua/mason-registry/init.lua index 535e6b57..5806c30a 100644 --- a/lua/mason-registry/init.lua +++ b/lua/mason-registry/init.lua @@ -49,7 +49,6 @@ function Registry.get_installed_package_names() directories[#directories + 1] = entry.name end end - -- TODO: validate that entry is a mason package return directories end @@ -68,7 +67,10 @@ function Registry.get_all_package_names() end function Registry.get_all_packages() - return vim.tbl_map(Registry.get_package, Registry.get_all_package_names()) + local _ = require "mason-core.functional" + local packages = + _.uniq_by(_.identity, _.concat(Registry.get_all_package_names(), Registry.get_installed_package_names())) + return vim.tbl_map(Registry.get_package, packages) end function Registry.get_all_package_specs() diff --git a/lua/mason-registry/sources/github.lua b/lua/mason-registry/sources/github.lua index 597e7d84..2b177bdd 100644 --- a/lua/mason-registry/sources/github.lua +++ b/lua/mason-registry/sources/github.lua @@ -1,5 +1,4 @@ local InstallLocation = require "mason-core.installer.InstallLocation" -local Optional = require "mason-core.optional" local Result = require "mason-core.result" local _ = require "mason-core.functional" local fetch = require "mason-core.fetch" diff --git a/lua/mason-registry/sources/init.lua b/lua/mason-registry/sources/init.lua index 765d4904..f468a8ef 100644 --- a/lua/mason-registry/sources/init.lua +++ b/lua/mason-registry/sources/init.lua @@ -11,7 +11,7 @@ local log = require "mason-core.log" ---@field serialize fun(self: RegistrySource): InstallReceiptRegistry ---@field is_same_location fun(self: RegistrySource, other: RegistrySource): boolean ----@alias RegistrySourceType '"github"' | '"lua"' | '"file"' +---@alias RegistrySourceType '"github"' | '"lua"' | '"file"' | '"synthesized"' ---@class LazySource ---@field type RegistrySourceType @@ -54,6 +54,11 @@ function LazySource.File(id) } end +function LazySource.Synthesized() + local SynthesizedSource = require "mason-registry.sources.synthesized" + return SynthesizedSource:new() +end + ---@param type RegistrySourceType ---@param id string ---@param init fun(id: string): RegistrySource @@ -115,6 +120,7 @@ end ---@class LazySourceCollection ---@field list LazySource[] +---@field synthesized LazySource local LazySourceCollection = {} LazySourceCollection.__index = LazySourceCollection @@ -123,6 +129,7 @@ function LazySourceCollection:new() local instance = {} setmetatable(instance, self) instance.list = {} + instance.synthesized = LazySource:new("synthesized", "synthesized", LazySource.Synthesized) return instance end @@ -184,7 +191,7 @@ function LazySourceCollection:checksum() return vim.fn.sha256(table.concat(registry_ids, "")) end ----@param opts? { include_uninstalled?: boolean } +---@param opts? { include_uninstalled?: boolean, include_synthesized?: boolean } function LazySourceCollection:iterate(opts) opts = opts or {} @@ -197,6 +204,12 @@ function LazySourceCollection:iterate(opts) return source end end + + -- We've exhausted the true registry sources, fall back to the synthesized registry source. + if idx == #self.list + 1 and opts.include_synthesized ~= false then + idx = idx + 1 + return self.synthesized:get() + end end end diff --git a/lua/mason-registry/sources/synthesized.lua b/lua/mason-registry/sources/synthesized.lua new file mode 100644 index 00000000..75638cd6 --- /dev/null +++ b/lua/mason-registry/sources/synthesized.lua @@ -0,0 +1,109 @@ +local Package = require "mason-core.package" +local Result = require "mason-core.result" +local _ = require "mason-core.functional" +local InstallReceipt = require("mason-core.receipt").InstallReceipt +local InstallLocation = require "mason-core.installer.InstallLocation" +local fs = require "mason-core.fs" +local log = require "mason-core.log" + +---@class SynthesizedRegistrySource : RegistrySource +---@field buffer table<string, Package> +local SynthesizedRegistrySource = {} +SynthesizedRegistrySource.__index = SynthesizedRegistrySource + +function SynthesizedRegistrySource:new() + ---@type SynthesizedRegistrySource + local instance = {} + setmetatable(instance, self) + instance.buffer = {} + return instance +end + +function SynthesizedRegistrySource:is_installed() + return true +end + +---@return RegistryPackageSpec[] +function SynthesizedRegistrySource:get_all_package_specs() + return {} +end + +---@param pkg_name string +---@param receipt InstallReceipt +---@return Package +function SynthesizedRegistrySource:load_package(pkg_name, receipt) + local installed_version = receipt:get_installed_package_version() + local source = { + id = ("pkg:mason/%s@%s"):format(pkg_name, installed_version or "N%2FA"), -- N%2FA = N/A + install = function() + error("This package can no longer be installed because it has been removed from the registry.", 0) + end, + } + ---@type RegistryPackageSpec + local spec = { + schema = "registry+v1", + name = pkg_name, + description = "", + categories = {}, + languages = {}, + homepage = "", + licenses = {}, + deprecation = { + since = receipt:get_installed_package_version() or "N/A", + message = "This package has been removed from the registry.", + }, + source = source, + } + local existing_pkg = self.buffer[pkg_name] + if existing_pkg then + existing_pkg:update(spec, self) + return existing_pkg + else + local pkg = Package:new(spec, self) + self.buffer[pkg_name] = pkg + return pkg + end +end + +---@param pkg_name string +---@return Package? +function SynthesizedRegistrySource:get_package(pkg_name) + local receipt_path = InstallLocation.global():receipt(pkg_name) + if fs.sync.file_exists(receipt_path) then + local ok, receipt_json = pcall(vim.json.decode, fs.sync.read_file(receipt_path)) + if ok then + local receipt = InstallReceipt.from_json(receipt_json) + return self:load_package(pkg_name, receipt) + else + log.error("Failed to decode package receipt", pkg_name, receipt_json) + end + end +end + +function SynthesizedRegistrySource:get_all_package_names() + return vim.tbl_keys(self.buffer) +end + +---@async +function SynthesizedRegistrySource:install() + return Result.success() +end + +function SynthesizedRegistrySource:get_display_name() + return "SynthesizedRegistrySource" +end + +function SynthesizedRegistrySource:serialize() + return {} +end + +---@param other SynthesizedRegistrySource +function SynthesizedRegistrySource:is_same_location(other) + return true +end + +function SynthesizedRegistrySource:__tostring() + return "SynthesizedRegistrySource" +end + +return SynthesizedRegistrySource diff --git a/lua/mason/ui/instance.lua b/lua/mason/ui/instance.lua index d0026389..476bdf8c 100644 --- a/lua/mason/ui/instance.lua +++ b/lua/mason/ui/instance.lua @@ -660,7 +660,7 @@ end local function update_registry_info() local registries = {} - for source in registry.sources:iterate { include_uninstalled = true } do + for source in registry.sources:iterate { include_uninstalled = true, include_synthesized = false } do table.insert(registries, { name = source:get_display_name(), is_installed = source:is_installed(), diff --git a/tests/mason-registry/sources/collection_spec.lua b/tests/mason-registry/sources/collection_spec.lua index b603c868..a5253ab5 100644 --- a/tests/mason-registry/sources/collection_spec.lua +++ b/tests/mason-registry/sources/collection_spec.lua @@ -1,4 +1,5 @@ local LazySourceCollection = require "mason-registry.sources" +local SynthesizedSource = require "mason-registry.sources.synthesized" describe("LazySourceCollection", function() it("should dedupe registries on append/prepend", function() @@ -18,4 +19,22 @@ describe("LazySourceCollection", function() assert.same("github:mason-org/mason-registry@2025-05-16", coll:get(3):get_full_id()) assert.same("file:~/registry", coll:get(4):get_full_id()) end) + + it("should fall back to synthesized source", function() + local coll = LazySourceCollection:new() + + for source in coll:iterate() do + assert.is_true(getmetatable(source) == SynthesizedSource) + return + end + error "Did not fall back to synthesized source" + end) + + it("should exclude synthesized source", function() + local coll = LazySourceCollection:new() + + for source in coll:iterate { include_synthesized = false } do + error "Should not iterate." + end + end) end) |
