Skip to content

Commit f38e0e4

Browse files
committed
feat: warn about semver-like version on vim.pack.add error
When vim.pack.add fails, detect if user has semver-like version strings (e.g., "1.*", "^1.0.0") and suggest using sem_version instead. This helps users who may have confused the version field (expects branch/tag/commit) with sem_version (semver ranges). Also: - Add utils.notify with consistent [zpack.nvim] prefix - Remove redundant [zpack] prefix from deprecation messages - Fix test framework to cleanup on failure (prevent cascading)
1 parent c301f59 commit f38e0e4

File tree

8 files changed

+189
-7
lines changed

8 files changed

+189
-7
lines changed

lua/zpack/deprecation.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ M.notify_removed = function(key)
3838
local entry = M.removed[key]
3939
if not entry then return end
4040
utils.schedule_notify(
41-
("[zpack] REMOVED: %s\n\n%s"):format(entry.message, entry.replacement),
41+
("REMOVED: %s\n\n%s"):format(entry.message, entry.replacement),
4242
vim.log.levels.WARN
4343
)
4444
end
@@ -47,7 +47,7 @@ M.notify_deprecated = function(key)
4747
local entry = M.deprecated[key]
4848
if not entry then return end
4949
utils.schedule_notify(
50-
("[zpack] DEPRECATED: %s\n\n%s"):format(entry.message, entry.replacement),
50+
("DEPRECATED: %s\n\n%s"):format(entry.message, entry.replacement),
5151
vim.log.levels.WARN
5252
)
5353
end

lua/zpack/init.lua

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ end
2727

2828
local function check_version()
2929
if vim.fn.has('nvim-0.12') ~= 1 then
30-
vim.schedule(function()
31-
vim.notify('zpack.nvim requires Neovim 0.12+', vim.log.levels.ERROR)
32-
end)
30+
require('zpack.utils').schedule_notify('requires Neovim 0.12+', vim.log.levels.ERROR)
3331
return false
3432
end
3533
return true

lua/zpack/registration.lua

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ local M = {}
66

77
---@param ctx zpack.ProcessContext
88
M.register_all = function(ctx)
9-
vim.pack.add(ctx.vim_packs, {
9+
local ok, err = pcall(vim.pack.add, ctx.vim_packs, {
1010
confirm = ctx.confirm,
1111
load = function(plugin)
1212
local pack_spec = plugin.spec
@@ -43,6 +43,24 @@ M.register_all = function(ctx)
4343
end
4444
})
4545

46+
if not ok then
47+
local semver_like_specs = {}
48+
for _, pack_spec in ipairs(ctx.vim_packs) do
49+
if pack_spec.version and utils.is_semver_like(pack_spec.version) then
50+
table.insert(semver_like_specs, pack_spec)
51+
end
52+
end
53+
for _, pack_spec in ipairs(semver_like_specs) do
54+
utils.schedule_notify(
55+
('Is `version = "%s"` for %s meant to be a semver range?\n'
56+
.. 'Consider using `sem_version = "%s"` or `version = vim.version.range("%s")` instead.')
57+
:format(pack_spec.version, pack_spec.src, pack_spec.version, pack_spec.version),
58+
vim.log.levels.WARN
59+
)
60+
end
61+
error(err)
62+
end
63+
4664
table.sort(ctx.registered_startup_packs, utils.compare_priority)
4765
table.sort(ctx.registered_lazy_packs, utils.compare_priority)
4866
table.sort(state.registered_plugin_names, function(a, b) return a:lower() < b:lower() end)

lua/zpack/utils.lua

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,13 @@ M.reset_lsdir_cache = function()
4242
lsdir_cache = {}
4343
end
4444

45+
M.notify = function(msg, level)
46+
vim.notify('[zpack.nvim] ' .. msg, level)
47+
end
48+
4549
M.schedule_notify = function(msg, level)
4650
vim.schedule(function()
47-
vim.notify(msg, level)
51+
M.notify(msg, level)
4852
end)
4953
end
5054

@@ -168,6 +172,21 @@ M.normalize_name = function(name)
168172
return norm
169173
end
170174

175+
---Check if a string looks like a semver range (not a branch/tag/commit)
176+
---@param str any
177+
---@return boolean
178+
M.is_semver_like = function(str)
179+
if type(str) ~= 'string' then
180+
return false
181+
end
182+
return str:match('%*') ~= nil
183+
or str:match('^%d[%d%.]*%.[xX]$') ~= nil
184+
or str:match('^%d[%d%.]*%.%a') ~= nil
185+
or str:match('^[>=<^~]') ~= nil
186+
or str:match('[>=<]') ~= nil
187+
or str:match('^%d+[%d%.]*$') ~= nil
188+
end
189+
171190
---Normalize plugin version using priority: version > sem_version > branch > tag > commit
172191
---@param spec zpack.Spec
173192
---@return string|vim.VersionRange|nil version

tests/helpers.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---@diagnostic disable: duplicate-set-field
12
local M = {}
23

34
M.test_results = {}
@@ -74,6 +75,7 @@ function M.test(name, fn)
7475
table.insert(M.test_results, { name = name, passed = false, error = err })
7576
print(string.format("✗ %s", name))
7677
print(string.format(" Error: %s", err))
78+
M.cleanup_test_env()
7779
end
7880
end
7981

tests/module_loader_test.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---@diagnostic disable: duplicate-set-field
12
local helpers = require('helpers')
23

34
return function()

tests/utils_test.lua

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,74 @@ return function()
118118
helpers.cleanup_test_env()
119119
end)
120120
end)
121+
122+
helpers.describe("is_semver_like utility", function()
123+
helpers.test("detects wildcard patterns", function()
124+
helpers.setup_test_env()
125+
local utils = require('zpack.utils')
126+
127+
helpers.assert_equal(utils.is_semver_like('1.*'), true)
128+
helpers.assert_equal(utils.is_semver_like('*'), true)
129+
helpers.assert_equal(utils.is_semver_like('1.2.*'), true)
130+
helpers.assert_equal(utils.is_semver_like('1.2.x'), true)
131+
helpers.assert_equal(utils.is_semver_like('1.x'), true)
132+
helpers.assert_equal(utils.is_semver_like('1.X'), true)
133+
helpers.assert_equal(utils.is_semver_like('1.2.X'), true)
134+
helpers.assert_equal(utils.is_semver_like('1.2.y'), true)
135+
helpers.assert_equal(utils.is_semver_like('1.a.b'), true)
136+
137+
helpers.cleanup_test_env()
138+
end)
139+
140+
helpers.test("detects range operators", function()
141+
helpers.setup_test_env()
142+
local utils = require('zpack.utils')
143+
144+
helpers.assert_equal(utils.is_semver_like('>=1.0.0'), true)
145+
helpers.assert_equal(utils.is_semver_like('<=2.0.0'), true)
146+
helpers.assert_equal(utils.is_semver_like('>1.0'), true)
147+
helpers.assert_equal(utils.is_semver_like('<2.0'), true)
148+
helpers.assert_equal(utils.is_semver_like('^1.0.0'), true)
149+
helpers.assert_equal(utils.is_semver_like('~1.0.0'), true)
150+
helpers.assert_equal(utils.is_semver_like('foo=bar'), true)
151+
152+
helpers.cleanup_test_env()
153+
end)
154+
155+
helpers.test("detects bare semver patterns", function()
156+
helpers.setup_test_env()
157+
local utils = require('zpack.utils')
158+
159+
helpers.assert_equal(utils.is_semver_like('1.0.0'), true)
160+
helpers.assert_equal(utils.is_semver_like('1.2'), true)
161+
helpers.assert_equal(utils.is_semver_like('1.2.3.4'), true)
162+
163+
helpers.cleanup_test_env()
164+
end)
165+
166+
helpers.test("returns false for branch/tag names", function()
167+
helpers.setup_test_env()
168+
local utils = require('zpack.utils')
169+
170+
helpers.assert_equal(utils.is_semver_like('main'), false)
171+
helpers.assert_equal(utils.is_semver_like('master'), false)
172+
helpers.assert_equal(utils.is_semver_like('v1.0.0'), false)
173+
helpers.assert_equal(utils.is_semver_like('v1.x'), false)
174+
helpers.assert_equal(utils.is_semver_like('release-1.0'), false)
175+
helpers.assert_equal(utils.is_semver_like('feature/foo'), false)
176+
177+
helpers.cleanup_test_env()
178+
end)
179+
180+
helpers.test("returns false for non-strings", function()
181+
helpers.setup_test_env()
182+
local utils = require('zpack.utils')
183+
184+
helpers.assert_equal(utils.is_semver_like(nil), false)
185+
helpers.assert_equal(utils.is_semver_like(123), false)
186+
helpers.assert_equal(utils.is_semver_like({}), false)
187+
188+
helpers.cleanup_test_env()
189+
end)
190+
end)
121191
end

tests/version_test.lua

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---@diagnostic disable: duplicate-set-field
12
local helpers = require('helpers')
23

34
return function()
@@ -213,5 +214,78 @@ return function()
213214

214215
helpers.cleanup_test_env()
215216
end)
217+
218+
helpers.test("semver-like version emits warning when vim.pack.add fails", function()
219+
helpers.setup_test_env()
220+
221+
local mock_vim_pack_add = vim.pack.add
222+
vim.pack.add = function()
223+
error("some error from vim.pack.add")
224+
end
225+
226+
local ok = pcall(function()
227+
require('zpack').setup({
228+
spec = {
229+
{
230+
'test/plugin',
231+
version = '1.*',
232+
},
233+
},
234+
defaults = { confirm = false },
235+
})
236+
end)
237+
238+
helpers.flush_pending()
239+
helpers.assert_equal(ok, false, "setup should fail when vim.pack.add throws")
240+
helpers.assert_equal(#_G.test_state.notifications >= 1, true, "warning should be emitted")
241+
242+
local found_warning = false
243+
for _, notif in ipairs(_G.test_state.notifications) do
244+
if notif.msg:match('%[zpack%.nvim%]') and notif.msg:match('sem_version') and notif.msg:match('1%.%*') then
245+
found_warning = true
246+
break
247+
end
248+
end
249+
helpers.assert_equal(found_warning, true, "warning should have [zpack.nvim] prefix and mention sem_version")
250+
251+
vim.pack.add = mock_vim_pack_add
252+
helpers.cleanup_test_env()
253+
end)
254+
255+
helpers.test("no warning when vim.pack.add fails but no semver-like versions", function()
256+
helpers.setup_test_env()
257+
258+
local mock_vim_pack_add = vim.pack.add
259+
vim.pack.add = function()
260+
error("some error from vim.pack.add")
261+
end
262+
263+
local ok = pcall(function()
264+
require('zpack').setup({
265+
spec = {
266+
{
267+
'test/plugin',
268+
version = 'nonexistent-branch',
269+
},
270+
},
271+
defaults = { confirm = false },
272+
})
273+
end)
274+
275+
helpers.flush_pending()
276+
helpers.assert_equal(ok, false, "setup should fail when vim.pack.add throws")
277+
278+
local found_warning = false
279+
for _, notif in ipairs(_G.test_state.notifications) do
280+
if notif.msg:match('sem_version') then
281+
found_warning = true
282+
break
283+
end
284+
end
285+
helpers.assert_equal(found_warning, false, "no warning should be emitted for non-semver-like versions")
286+
287+
vim.pack.add = mock_vim_pack_add
288+
helpers.cleanup_test_env()
289+
end)
216290
end)
217291
end

0 commit comments

Comments
 (0)