8 Commits

Author SHA1 Message Date
9463ba2b4b Merge pull request #2 from taigrr/cd/ci-and-auto-install-fixes
fix(runner): batch auto-install and add CI
2026-04-12 10:28:56 +02:00
bb82cde133 fix(runner): batch auto-install to prevent race conditions and silent UI
- Auto-install now batches all registrations into a single deferred
  install call (200ms debounce), preventing race conditions where
  multiple deferred installs would reject each other
- Silent auto-install no longer pops open the Glaze UI window
- runner.install() accepts { silent: true } option to suppress UI
  and notifications
- Add CI workflow (stylua format check + luacheck lint)
- Add .stylua.toml and .luacheckrc configs
- Fix empty if branch lint warning in runner.lua
- Apply stylua formatting to checker.lua and health.lua
2026-04-05 06:04:20 +00:00
9e7f655026 Merge pull request #1 from taigrr/cd/bugfixes-and-cleanup
fix(health,checker): fix plugin display bug and remove redundant version checks
2026-03-02 04:04:25 -05:00
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
93acbc55c5 update menu labels 2026-02-19 01:18:11 -05:00
d94fee916b handle duplicate binaries 2026-02-19 01:07:47 -05:00
ea73ed51b1 Update badge URLs in README.md 2026-02-18 23:33:18 -05:00
5867abd592 Update copyright year in LICENSE file 2026-02-18 23:27:29 -05:00
10 changed files with 202 additions and 56 deletions

25
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check formatting with StyLua
uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
args: --check .
- name: Lint with luacheck
uses: lunarmodules/luacheck@v1
with:
args: lua/

7
.luacheckrc Normal file
View File

@@ -0,0 +1,7 @@
std = "luajit"
globals = { "vim" }
max_line_length = 150
ignore = {
"212", -- unused argument (common in callbacks)
}

6
.stylua.toml Normal file
View File

@@ -0,0 +1,6 @@
column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
call_parentheses = "Always"

View File

@@ -1,6 +1,6 @@
BSD Zero Clause License BSD Zero Clause License
Copyright (c) 2025 Tai Groot Copyright (c) 2026 Tai Groot
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted. purpose with or without fee is hereby granted.

View File

@@ -6,16 +6,16 @@
<p> <p>
<a href="https://github.com/taigrr/glaze.nvim/releases/latest"> <a href="https://github.com/taigrr/glaze.nvim/releases/latest">
<img alt="Latest release" src="https://img.shields.io/github/v/release/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=FF6AD5&logoColor=D9E0EE&labelColor=302D41&include_prerelease&sort=semver"> <img alt="Latest release" src="https://img.shields.io/github/v/release/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=FF6AD5&logoColor=D9E0EE&labelColor=302D41&sort=semver&include_prerelease">
</a> </a>
<a href="https://github.com/taigrr/glaze.nvim/pulse"> <a href="https://github.com/taigrr/glaze.nvim/pulse">
<img alt="Last commit" src="https://img.shields.io/github/last-commit/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=8bd5ca&logoColor=D9E0EE&labelColor=302D41"> <img alt="Last commit" src="https://img.shields.io/github/last-commit/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=8bd5ca&labelColor=302D41&logoColor=D9E0EE">
</a> </a>
<a href="https://github.com/taigrr/glaze.nvim/blob/master/LICENSE"> <a href="https://github.com/taigrr/glaze.nvim/blob/master/LICENSE">
<img alt="License" src="https://img.shields.io/github/license/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=ee999f&logoColor=D9E0EE&labelColor=302D41"> <img alt="License" src="https://img.shields.io/github/license/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=ee999f&labelColor=302D41&logoColor=D9E0EE">
</a> </a>
<a href="https://github.com/taigrr/glaze.nvim/stargazers"> <a href="https://github.com/taigrr/glaze.nvim/stargazers">
<img alt="Stars" src="https://img.shields.io/github/stars/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=c69ff5&logoColor=D9E0EE&labelColor=302D41"> <img alt="Stars" src="https://img.shields.io/github/stars/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=c69ff5&labelColor=302D41&logoColor=D9E0EE">
</a> </a>
</p> </p>

View File

@@ -127,6 +127,61 @@ function M.get_update_info()
return M._update_info return M._update_info
end 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. ---Check for updates on all registered binaries.
---@param opts? { silent?: boolean } ---@param opts? { silent?: boolean }
function M.check(opts) function M.check(opts)
@@ -149,12 +204,9 @@ function M.check(opts)
end end
M._checking = true M._checking = true
local remaining = 0
local updates_found = 0 local updates_found = 0
local remaining = vim.tbl_count(binaries)
for name, _ in pairs(binaries) do for name, binary in pairs(binaries) do
remaining = remaining + 2 -- installed version + latest version
local info = { local info = {
name = name, name = name,
installed_version = nil, installed_version = nil,
@@ -163,16 +215,6 @@ function M.check(opts)
} }
M._update_info[name] = info M._update_info[name] = info
get_installed_version(name, function()
-- callback receives version from the jobstart; re-check via closure
end)
end
-- Simplified: check each binary sequentially-ish
remaining = vim.tbl_count(binaries)
for name, binary in pairs(binaries) do
local info = M._update_info[name]
get_installed_version(name, function(installed) get_installed_version(name, function(installed)
info.installed_version = installed info.installed_version = installed

View File

@@ -72,10 +72,11 @@ function M.check()
else else
vim.health.ok(count .. " binary(ies) registered") vim.health.ok(count .. " binary(ies) registered")
for name, binary in pairs(binaries) do for name, binary in pairs(binaries) do
local pi = (binary.plugins and #binary.plugins > 0) and (" (" .. table.concat(binary.plugins, ", ") .. ")") or ""
if glaze.is_installed(name) then if glaze.is_installed(name) then
vim.health.ok(" " .. name .. " — installed" .. (binary.plugin and (" (" .. binary.plugin .. ")") or "")) vim.health.ok(" " .. name .. " — installed" .. pi)
else else
vim.health.warn(" " .. name .. " — missing" .. (binary.plugin and (" (" .. binary.plugin .. ")") or ""), { vim.health.warn(" " .. name .. " — missing" .. pi, {
"Run :GlazeInstall " .. name, "Run :GlazeInstall " .. name,
"Or: go install " .. binary.url .. "@latest", "Or: go install " .. binary.url .. "@latest",
}) })

View File

@@ -15,8 +15,8 @@ local M = {}
---@class GlazeBinary ---@class GlazeBinary
---@field name string Binary name (executable name) ---@field name string Binary name (executable name)
---@field url string Go module URL (without @version) ---@field url string Go module URL (without @version)
---@field plugin? string Plugin that registered this binary ---@field plugins string[] Plugins that registered this binary
---@field callback? fun(success: boolean) Optional callback after install/update ---@field callbacks table<string, fun(success: boolean)> Callbacks keyed by plugin name
---@class GlazeAutoCheckConfig ---@class GlazeAutoCheckConfig
---@field enabled? boolean Whether to auto-check for updates ---@field enabled? boolean Whether to auto-check for updates
@@ -85,6 +85,12 @@ M._binaries = {}
---@type number? ---@type number?
M._ns = nil M._ns = nil
---@type table<string, boolean> Binaries queued for auto-install
M._auto_install_queue = {}
---@type number? Timer for batched auto-install
M._auto_install_timer = nil
---@param opts? GlazeConfig ---@param opts? GlazeConfig
function M.setup(opts) function M.setup(opts)
M.config = vim.tbl_deep_extend("force", M.config, opts or {}) M.config = vim.tbl_deep_extend("force", M.config, opts or {})
@@ -147,23 +153,68 @@ end
---@param opts? { plugin?: string, callback?: fun(success: boolean) } ---@param opts? { plugin?: string, callback?: fun(success: boolean) }
function M.register(name, url, opts) function M.register(name, url, opts)
opts = opts or {} opts = opts or {}
local plugin = opts.plugin or "unknown"
-- Check if this URL is already registered under a different name
for existing_name, binary in pairs(M._binaries) do
if binary.url == url and existing_name ~= name then
-- Same URL, different name - merge into existing entry
if not vim.tbl_contains(binary.plugins, plugin) then
table.insert(binary.plugins, plugin)
end
if opts.callback then
binary.callbacks[plugin] = opts.callback
end
return
end
end
-- Check if this name is already registered
local existing = M._binaries[name]
if existing then
-- Merge plugin into existing entry
if not vim.tbl_contains(existing.plugins, plugin) then
table.insert(existing.plugins, plugin)
end
if opts.callback then
existing.callbacks[plugin] = opts.callback
end
return
end
-- New binary registration
M._binaries[name] = { M._binaries[name] = {
name = name, name = name,
url = url, url = url,
plugin = opts.plugin, plugins = opts.plugin and { opts.plugin } or {},
callback = opts.callback, callbacks = opts.callback and { [plugin] = opts.callback } or {},
} }
-- Auto-install if enabled and binary is missing -- Queue for auto-install if enabled and binary is missing.
-- Uses a batched timer so multiple registers don't race.
if M.config.auto_install.enabled and not M.is_installed(name) then if M.config.auto_install.enabled and not M.is_installed(name) then
vim.defer_fn(function() M._auto_install_queue[name] = true
if not M.is_installed(name) then if M._auto_install_timer then
if not M.config.auto_install.silent then vim.fn.timer_stop(M._auto_install_timer)
vim.notify("[glaze] Auto-installing " .. name .. "", vim.log.levels.INFO) end
M._auto_install_timer = vim.fn.timer_start(200, function()
M._auto_install_timer = nil
vim.schedule(function()
local to_install = {}
for queued_name, _ in pairs(M._auto_install_queue) do
if not M.is_installed(queued_name) then
table.insert(to_install, queued_name)
end
end end
require("glaze.runner").install({ name }) M._auto_install_queue = {}
end if #to_install > 0 then
end, 100) if not M.config.auto_install.silent then
vim.notify("[glaze] Auto-installing " .. table.concat(to_install, ", ") .. "", vim.log.levels.INFO)
end
require("glaze.runner").install(to_install, { silent = M.config.auto_install.silent })
end
end)
end)
end end
end end

View File

@@ -93,10 +93,17 @@ local function run_task(task)
task.status = code == 0 and "done" or "error" task.status = code == 0 and "done" or "error"
task.job_id = nil task.job_id = nil
-- Call binary callback if set -- Refresh version info on success
if task.binary.callback then if code == 0 then
require("glaze.checker").refresh_version(task.binary.name)
end
-- Call all registered callbacks
if task.binary.callbacks then
vim.schedule(function() vim.schedule(function()
task.binary.callback(code == 0) for _, cb in pairs(task.binary.callbacks) do
cb(code == 0)
end
end) end)
end end
@@ -136,12 +143,16 @@ end
---Run tasks for specified binaries. ---Run tasks for specified binaries.
---@param names string[] ---@param names string[]
---@param mode "install"|"update" ---@param mode "install"|"update"
local function run(names, mode) ---@param opts? { silent?: boolean }
local function run(names, mode, opts)
opts = opts or {}
local glaze = require("glaze") local glaze = require("glaze")
-- Reject if already running (race condition fix) -- Reject if already running (race condition fix)
if M._running then if M._running then
vim.notify("Glaze: tasks already running. Wait or abort first.", vim.log.levels.WARN) if not opts.silent then
vim.notify("Glaze: tasks already running. Wait or abort first.", vim.log.levels.WARN)
end
return return
end end
@@ -157,18 +168,18 @@ local function run(names, mode)
for _, name in ipairs(names) do for _, name in ipairs(names) do
local binary = glaze._binaries[name] local binary = glaze._binaries[name]
if binary then if binary then
if mode == "install" and glaze.is_installed(name) then if not (mode == "install" and glaze.is_installed(name)) then
-- Skip already installed
else
table.insert(binaries, binary) table.insert(binaries, binary)
end end
else else
vim.notify("Unknown binary: " .. name, vim.log.levels.WARN) if not opts.silent then
vim.notify("Unknown binary: " .. name, vim.log.levels.WARN)
end
end end
end end
if #binaries == 0 then if #binaries == 0 then
if mode == "install" then if mode == "install" and not opts.silent then
vim.notify("All binaries already installed", vim.log.levels.INFO) vim.notify("All binaries already installed", vim.log.levels.INFO)
end end
return return
@@ -184,8 +195,10 @@ local function run(names, mode)
M._notify() M._notify()
M._process_queue() M._process_queue()
-- Open UI -- Open UI (skip for silent/auto-install operations)
require("glaze.view").open() if not opts.silent then
require("glaze.view").open()
end
end end
---Update specific binaries. ---Update specific binaries.
@@ -202,8 +215,9 @@ end
---Install specific binaries. ---Install specific binaries.
---@param names string[] ---@param names string[]
function M.install(names) ---@param opts? { silent?: boolean }
run(names, "install") function M.install(names, opts)
run(names, "install", opts)
end end
---Install all missing binaries. ---Install all missing binaries.

View File

@@ -164,16 +164,16 @@ function M.render()
text:append(" ", nil, { indent = 2 }) text:append(" ", nil, { indent = 2 })
text:append(" U ", "GlazeButtonActive"):append(" Update All ", "GlazeButton") text:append(" U ", "GlazeButtonActive"):append(" Update All ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" u ", "GlazeButtonActive"):append(" Update ", "GlazeButton") text:append(" u ", "GlazeButtonActive"):append(" Update Selected ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" I ", "GlazeButtonActive"):append(" Install All ", "GlazeButton") text:append(" I ", "GlazeButtonActive"):append(" Install All ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" i ", "GlazeButtonActive"):append(" Install ", "GlazeButton") text:append(" i ", "GlazeButtonActive"):append(" Install Selected ", "GlazeButton")
text:nl() text:nl()
text:append(" ", nil, { indent = 2 }) text:append(" ", nil, { indent = 2 })
text:append(" x ", "GlazeButtonActive"):append(" Abort ", "GlazeButton") text:append(" x ", "GlazeButtonActive"):append(" Abort ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" ", "GlazeButtonActive"):append(" Details ", "GlazeButton") text:append(" <enter> ", "GlazeButtonActive"):append(" Details ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" q ", "GlazeButtonActive"):append(" Close ", "GlazeButton") text:append(" q ", "GlazeButtonActive"):append(" Close ", "GlazeButton")
text:nl():nl() text:nl():nl()
@@ -323,8 +323,8 @@ function M._render_binary(text, binary, icons, update_info)
text:append(" " .. icon .. " ", icon_hl) text:append(" " .. icon .. " ", icon_hl)
text:append(binary.name, "GlazeBinary") text:append(binary.name, "GlazeBinary")
if binary.plugin then if binary.plugins and #binary.plugins > 0 then
text:append(" (" .. binary.plugin .. ")", "GlazePlugin") text:append(" (" .. table.concat(binary.plugins, ", ") .. ")", "GlazePlugin")
end end
-- Show update available indicator -- Show update available indicator
@@ -353,9 +353,9 @@ function M._render_binary(text, binary, icons, update_info)
text:append(bin_path, "GlazeUrl"):nl() text:append(bin_path, "GlazeUrl"):nl()
end end
if binary.plugin then if binary.plugins and #binary.plugins > 0 then
text:append("Plugin: ", "GlazeComment", { indent = 6 }) text:append("Plugins: ", "GlazeComment", { indent = 6 })
text:append(binary.plugin, "GlazePlugin"):nl() text:append(table.concat(binary.plugins, ", "), "GlazePlugin"):nl()
end end
-- Show last error output from tasks -- Show last error output from tasks