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
This commit is contained in:
2026-04-05 06:04:20 +00:00
parent 9e7f655026
commit bb82cde133
7 changed files with 93 additions and 28 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

@@ -141,12 +141,13 @@ function M.refresh_version(name, callback)
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,
}
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

View File

@@ -72,9 +72,7 @@ function M.check()
else
vim.health.ok(count .. " binary(ies) registered")
for name, binary in pairs(binaries) do
local pi = (binary.plugins and #binary.plugins > 0)
and (" (" .. table.concat(binary.plugins, ", ") .. ")")
or ""
local pi = (binary.plugins and #binary.plugins > 0) and (" (" .. table.concat(binary.plugins, ", ") .. ")") or ""
if glaze.is_installed(name) then
vim.health.ok(" " .. name .. " — installed" .. pi)
else

View File

@@ -85,6 +85,12 @@ M._binaries = {}
---@type number?
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
function M.setup(opts)
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
@@ -184,16 +190,31 @@ function M.register(name, url, opts)
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
vim.defer_fn(function()
if not M.is_installed(name) then
if not M.config.auto_install.silent then
vim.notify("[glaze] Auto-installing " .. name .. "", vim.log.levels.INFO)
M._auto_install_queue[name] = true
if M._auto_install_timer then
vim.fn.timer_stop(M._auto_install_timer)
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
require("glaze.runner").install({ name })
end
end, 100)
M._auto_install_queue = {}
if #to_install > 0 then
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

View File

@@ -143,12 +143,16 @@ end
---Run tasks for specified binaries.
---@param names string[]
---@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")
-- Reject if already running (race condition fix)
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
end
@@ -164,18 +168,18 @@ local function run(names, mode)
for _, name in ipairs(names) do
local binary = glaze._binaries[name]
if binary then
if mode == "install" and glaze.is_installed(name) then
-- Skip already installed
else
if not (mode == "install" and glaze.is_installed(name)) then
table.insert(binaries, binary)
end
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
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)
end
return
@@ -191,8 +195,10 @@ local function run(names, mode)
M._notify()
M._process_queue()
-- Open UI
require("glaze.view").open()
-- Open UI (skip for silent/auto-install operations)
if not opts.silent then
require("glaze.view").open()
end
end
---Update specific binaries.
@@ -209,8 +215,9 @@ end
---Install specific binaries.
---@param names string[]
function M.install(names)
run(names, "install")
---@param opts? { silent?: boolean }
function M.install(names, opts)
run(names, "install", opts)
end
---Install all missing binaries.