35b9df9eba
## Why
Cloning plugins from git ties the editor to GitHub uptime and moving branches; archives at a pinned version from our own registry are reproducible and reachable offline.
## How
- Add a registry resolver interface `(spec, registry) -> {url, headers, filename, sha256?}` with builtin `template` and `artifactapi` kinds, a per-plugin registry override and a `url` escape hatch.
- Take auth tokens only from an env var or a `token_cmd`; reject a literal token at setup.
- Download with curl, verify an optional sha256, extract to a staging dir and rename into `pack/vimpack/{start,opt}/<name>`, restoring the previous tree if the swap fails.
- Track installed name/version/sha256 in `stdpath('data')/vimpack/manifest.json` and skip plugins already at the wanted version.
- Add `:VimpackInstall`, `:VimpackUpdate`, `:VimpackClean` and `:VimpackStatus`, running downloads through `vim.system` under a concurrency cap.
- Hash with a pure-Lua SHA-256; `vim.fn.sha256()` rejects binary archives with E976.
- Add a dependency-free `tests/minimal.lua` harness and 57 specs covering resolution, auth, manifest, planning, checksum mismatch, orphan detection and end-to-end install.
- Add `.woodpecker/{test,lint,pre-commit}.yaml`, luacheck/stylua config and pre-commit hooks.
93 lines
2.4 KiB
Lua
93 lines
2.4 KiB
Lua
-- Minimal busted-flavoured harness: no rocks, no vendored plugins.
|
|
-- Run with: nvim --headless --clean -l tests/minimal.lua tests/*_spec.lua
|
|
local root = vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':p:h:h')
|
|
vim.opt.runtimepath:prepend(root)
|
|
package.path = table.concat({ root .. '/lua/?.lua', root .. '/lua/?/init.lua', package.path }, ';')
|
|
|
|
local M = { passed = 0, failed = 0, failures = {} }
|
|
local stack = {}
|
|
|
|
local function describe(name, fn)
|
|
stack[#stack + 1] = name
|
|
fn()
|
|
stack[#stack] = nil
|
|
end
|
|
|
|
local function it(name, fn)
|
|
local label = table.concat(stack, ' ') .. ' ' .. name
|
|
local ok, err = pcall(fn)
|
|
if ok then
|
|
M.passed = M.passed + 1
|
|
io.write('ok ' .. label .. '\n')
|
|
else
|
|
M.failed = M.failed + 1
|
|
M.failures[#M.failures + 1] = label .. '\n ' .. tostring(err)
|
|
io.write('FAIL ' .. label .. '\n ' .. tostring(err) .. '\n')
|
|
end
|
|
end
|
|
|
|
local assert_eq = function(expected, got, msg)
|
|
if not vim.deep_equal(expected, got) then
|
|
error(
|
|
string.format('%sexpected %s, got %s', msg and (msg .. ': ') or '', vim.inspect(expected), vim.inspect(got)),
|
|
2
|
|
)
|
|
end
|
|
end
|
|
|
|
local assert_true = function(value, msg)
|
|
if not value then
|
|
error(msg or ('expected truthy, got ' .. vim.inspect(value)), 2)
|
|
end
|
|
end
|
|
|
|
local assert_nil = function(value, msg)
|
|
if value ~= nil then
|
|
error(msg or ('expected nil, got ' .. vim.inspect(value)), 2)
|
|
end
|
|
end
|
|
|
|
local assert_match = function(pattern, str, msg)
|
|
if type(str) ~= 'string' or not str:find(pattern, 1, true) then
|
|
error(string.format('%sexpected %s to contain %q', msg and (msg .. ': ') or '', vim.inspect(str), pattern), 2)
|
|
end
|
|
end
|
|
|
|
_G.describe = describe
|
|
_G.it = it
|
|
_G.assert_eq = assert_eq
|
|
_G.assert_true = assert_true
|
|
_G.assert_nil = assert_nil
|
|
_G.assert_match = assert_match
|
|
_G.tmpdir = function()
|
|
local dir = vim.fs.joinpath(vim.fn.tempname(), 'vimpack-test')
|
|
vim.fn.mkdir(dir, 'p')
|
|
return dir
|
|
end
|
|
|
|
local files = {}
|
|
for i = 1, #_G.arg do
|
|
files[#files + 1] = _G.arg[i]
|
|
end
|
|
if #files == 0 then
|
|
for name, kind in vim.fs.dir(root .. '/tests') do
|
|
if kind == 'file' and name:match('_spec%.lua$') then
|
|
files[#files + 1] = root .. '/tests/' .. name
|
|
end
|
|
end
|
|
table.sort(files)
|
|
end
|
|
|
|
for _, file in ipairs(files) do
|
|
dofile(file)
|
|
end
|
|
|
|
io.write(string.format('\n%d passed, %d failed\n', M.passed, M.failed))
|
|
if M.failed > 0 then
|
|
for _, failure in ipairs(M.failures) do
|
|
io.write('\n' .. failure .. '\n')
|
|
end
|
|
vim.cmd('cquit 1')
|
|
end
|
|
vim.cmd('quit')
|