Files
glaze.nvim/lua/glaze/checker.lua
Tai Groot 67255b346c fix(health,checker): fix plugin display bug and remove redundant version checks
- health.lua: referenced non-existent binary.plugin field instead of
  binary.plugins array, causing plugin names to never display in
  :checkhealth output
- checker.lua: removed redundant first loop in check() that called
  get_installed_version without using the result, and eliminated dead
  remaining counter that was immediately overwritten
2026-03-02 09:03:05 +00:00

314 lines
8.5 KiB
Lua

---@brief [[
--- glaze.nvim update checker
--- Checks for newer versions of registered Go binaries
---@brief ]]
local M = {}
---@class GlazeUpdateInfo
---@field name string Binary name
---@field installed_version? string Currently installed version
---@field latest_version? string Latest available version
---@field has_update boolean Whether an update is available
---@type table<string, GlazeUpdateInfo>
M._update_info = {}
---@type boolean
M._checking = false
local STATE_FILE = vim.fn.stdpath("data") .. "/glaze/state.json"
---Read persisted state from disk.
---@return table
local function read_state()
local ok, content = pcall(vim.fn.readfile, STATE_FILE)
if not ok or #content == 0 then
return {}
end
local decode_ok, data = pcall(vim.json.decode, table.concat(content, "\n"))
if not decode_ok then
return {}
end
return data or {}
end
---Write state to disk.
---@param state table
local function write_state(state)
local dir = vim.fn.fnamemodify(STATE_FILE, ":h")
vim.fn.mkdir(dir, "p")
local json = vim.json.encode(state)
vim.fn.writefile({ json }, STATE_FILE)
end
---Get the frequency in seconds from config.
---@return number seconds
local function get_frequency_seconds()
local glaze = require("glaze")
local freq = glaze.config.auto_check.frequency
if freq == "daily" then
return 86400
elseif freq == "weekly" then
return 604800
elseif type(freq) == "number" then
return freq * 3600
end
return 86400
end
---Get installed version of a binary by parsing `go version -m` output.
---@param name string Binary name
---@param callback fun(version: string?)
local function get_installed_version(name, callback)
local glaze = require("glaze")
local bin_path = glaze.bin_path(name)
if not bin_path then
callback(nil)
return
end
vim.fn.jobstart({ "go", "version", "-m", bin_path }, {
stdout_buffered = true,
on_stdout = function(_, data)
if not data then
callback(nil)
return
end
local output = table.concat(data, "\n")
-- Parse "mod\tmodule/path\tv1.2.3\th1:..." or "path\tmodule/path"
local version = output:match("\tmod\t[^\t]+\t(v[^\t%s]+)")
or output:match("\tpath\t[^\n]+\n[^\t]*\tmod\t[^\t]+\t(v[^\t%s]+)")
callback(version)
end,
on_exit = function(_, code)
if code ~= 0 then
callback(nil)
end
end,
})
end
---Check for the latest version of a module using go list.
---@param url string Module URL
---@param callback fun(version: string?)
local function get_latest_version(url, callback)
local glaze = require("glaze")
local cmd = vim.list_extend({}, glaze.config.go_cmd)
vim.list_extend(cmd, { "list", "-m", "-json", url .. "@latest" })
vim.fn.jobstart(cmd, {
stdout_buffered = true,
env = { GOFLAGS = "" },
on_stdout = function(_, data)
if not data then
callback(nil)
return
end
local output = table.concat(data, "\n")
local decode_ok, result = pcall(vim.json.decode, output)
if decode_ok and result and result.Version then
callback(result.Version)
else
callback(nil)
end
end,
on_exit = function(_, code)
if code ~= 0 then
callback(nil)
end
end,
})
end
---Get cached update info.
---@return table<string, GlazeUpdateInfo>
function M.get_update_info()
return M._update_info
end
---Refresh version info for a single binary (called after install/update).
---@param name string Binary name
---@param callback? fun() Optional callback when done
function M.refresh_version(name, callback)
local glaze = require("glaze")
local binary = glaze._binaries[name]
if not binary then
if callback then
callback()
end
return
end
get_installed_version(name, function(installed)
local info = M._update_info[name] or {
name = name,
installed_version = nil,
latest_version = nil,
has_update = false,
}
info.installed_version = installed
-- If we have latest version cached, check if still needs update
if info.latest_version and installed then
info.has_update = installed ~= info.latest_version
else
info.has_update = false
end
M._update_info[name] = info
-- Persist to state
local state = read_state()
state.update_info = state.update_info or {}
state.update_info[name] = {
installed_version = info.installed_version,
latest_version = info.latest_version,
has_update = info.has_update,
}
write_state(state)
-- Refresh UI if open
vim.schedule(function()
local ok, view = pcall(require, "glaze.view")
if ok and view._float and view._float:valid() then
view.render()
end
if callback then
callback()
end
end)
end)
end
---Check for updates on all registered binaries.
---@param opts? { silent?: boolean }
function M.check(opts)
opts = opts or {}
local glaze = require("glaze")
local binaries = glaze.binaries()
if vim.tbl_count(binaries) == 0 then
if not opts.silent then
vim.notify("Glaze: no binaries registered", vim.log.levels.INFO)
end
return
end
if M._checking then
if not opts.silent then
vim.notify("Glaze: already checking for updates", vim.log.levels.INFO)
end
return
end
M._checking = true
local updates_found = 0
local remaining = vim.tbl_count(binaries)
for name, binary in pairs(binaries) do
local info = {
name = name,
installed_version = nil,
latest_version = nil,
has_update = false,
}
M._update_info[name] = info
get_installed_version(name, function(installed)
info.installed_version = installed
get_latest_version(binary.url, function(latest)
info.latest_version = latest
if installed and latest and installed ~= latest then
info.has_update = true
updates_found = updates_found + 1
end
remaining = remaining - 1
if remaining <= 0 then
M._checking = false
-- Save check timestamp
local state = read_state()
state.last_check = os.time()
state.update_info = {}
for n, i in pairs(M._update_info) do
state.update_info[n] = {
installed_version = i.installed_version,
latest_version = i.latest_version,
has_update = i.has_update,
}
end
write_state(state)
-- Auto-update if enabled (requires auto_check to be enabled)
if updates_found > 0 and glaze.config.auto_update.enabled and glaze.config.auto_check.enabled then
vim.schedule(function()
local to_update = {}
for n, i in pairs(M._update_info) do
if i.has_update then
table.insert(to_update, n)
end
end
vim.notify("Glaze: auto-updating " .. #to_update .. " binary(ies)…", vim.log.levels.INFO)
require("glaze.runner").update(to_update)
end)
elseif not opts.silent then
if updates_found > 0 then
vim.schedule(function()
vim.notify(
"Glaze: " .. updates_found .. " update(s) available — run :GlazeUpdate",
vim.log.levels.INFO
)
end)
else
vim.schedule(function()
vim.notify("Glaze: all binaries up to date", vim.log.levels.INFO)
end)
end
elseif updates_found > 0 then
vim.schedule(function()
vim.notify("Glaze: " .. updates_found .. " update(s) available — run :GlazeUpdate", vim.log.levels.INFO)
end)
end
-- Refresh UI if open
vim.schedule(function()
local ok, view = pcall(require, "glaze.view")
if ok and view._float and view._float:valid() then
view.render()
end
end)
end
end)
end)
end
end
---Auto-check if enough time has passed since last check.
function M.auto_check()
local state = read_state()
local last_check = state.last_check or 0
local now = os.time()
local freq = get_frequency_seconds()
-- Load cached update info
if state.update_info then
for name, info in pairs(state.update_info) do
M._update_info[name] = {
name = name,
installed_version = info.installed_version,
latest_version = info.latest_version,
has_update = info.has_update or false,
}
end
end
if (now - last_check) >= freq then
M.check({ silent = true })
end
end
return M