aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lua/mason-core/installer/InstallHandle.lua2
-rw-r--r--lua/mason-core/installer/context/init.lua2
-rw-r--r--lua/mason-core/package/AbstractPackage.lua10
-rw-r--r--lua/mason-core/receipt.lua12
-rw-r--r--lua/mason-registry/init.lua6
-rw-r--r--lua/mason-registry/sources/github.lua1
-rw-r--r--lua/mason-registry/sources/init.lua17
-rw-r--r--lua/mason-registry/sources/synthesized.lua109
-rw-r--r--lua/mason/ui/instance.lua2
-rw-r--r--tests/mason-registry/sources/collection_spec.lua19
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)