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.
106 lines
3.5 KiB
Lua
106 lines
3.5 KiB
Lua
local fsmod = require('vimpack.fs')
|
|
|
|
describe('scan', function()
|
|
it('maps directories to their bucket', function()
|
|
local root = tmpdir()
|
|
vim.fn.mkdir(root .. '/start/a/lua', 'p')
|
|
vim.fn.mkdir(root .. '/opt/b', 'p')
|
|
vim.fn.writefile({ '' }, root .. '/start/loose.txt')
|
|
|
|
local found = fsmod.scan(root)
|
|
assert_eq(false, found.a.opt)
|
|
assert_eq(true, found.b.opt)
|
|
assert_nil(found['loose.txt'])
|
|
end)
|
|
|
|
it('returns nothing for a missing root', function()
|
|
assert_eq({}, fsmod.scan(tmpdir() .. '/absent'))
|
|
end)
|
|
end)
|
|
|
|
describe('target_dir', function()
|
|
it('splits start and opt', function()
|
|
assert_eq('/r/start/a', fsmod.target_dir({ name = 'a', dir_name = 'a', opt = false }, '/r'))
|
|
assert_eq('/r/opt/a', fsmod.target_dir({ name = 'a', dir_name = 'a', opt = true }, '/r'))
|
|
end)
|
|
end)
|
|
|
|
describe('strip_top_level', function()
|
|
it('hoists a single wrapper directory', function()
|
|
local root = tmpdir()
|
|
vim.fn.mkdir(root .. '/oil.nvim-2.11.0/lua', 'p')
|
|
assert_eq(root .. '/oil.nvim-2.11.0', fsmod.strip_top_level(root))
|
|
end)
|
|
|
|
it('leaves a flat archive alone', function()
|
|
local root = tmpdir()
|
|
vim.fn.mkdir(root .. '/lua', 'p')
|
|
vim.fn.mkdir(root .. '/plugin', 'p')
|
|
assert_eq(root, fsmod.strip_top_level(root))
|
|
end)
|
|
end)
|
|
|
|
describe('swap', function()
|
|
it('replaces an existing install', function()
|
|
local root = tmpdir()
|
|
vim.fn.mkdir(root .. '/target', 'p')
|
|
vim.fn.writefile({ 'old' }, root .. '/target/marker')
|
|
vim.fn.mkdir(root .. '/staged', 'p')
|
|
vim.fn.writefile({ 'new' }, root .. '/staged/marker')
|
|
|
|
assert_true(fsmod.swap(root .. '/staged', root .. '/target'))
|
|
assert_eq({ 'new' }, vim.fn.readfile(root .. '/target/marker'))
|
|
assert_true(not fsmod.exists(root .. '/target.vimpack-old'))
|
|
end)
|
|
|
|
it('keeps the previous install when the staged tree is missing', function()
|
|
local root = tmpdir()
|
|
vim.fn.mkdir(root .. '/target', 'p')
|
|
vim.fn.writefile({ 'old' }, root .. '/target/marker')
|
|
|
|
local ok, err = fsmod.swap(root .. '/absent', root .. '/target')
|
|
assert_true(not ok)
|
|
assert_match('cannot move new install into place', err)
|
|
assert_eq({ 'old' }, vim.fn.readfile(root .. '/target/marker'))
|
|
end)
|
|
end)
|
|
|
|
describe('extract', function()
|
|
local function wait_extract(archive, dest)
|
|
local result
|
|
fsmod.extract(archive, dest, function(ok, err)
|
|
result = { ok = ok, err = err }
|
|
end)
|
|
vim.wait(10000, function()
|
|
return result ~= nil
|
|
end)
|
|
return result
|
|
end
|
|
|
|
it('unpacks a tarball', function()
|
|
local root = tmpdir()
|
|
vim.fn.mkdir(root .. '/src/plug/lua', 'p')
|
|
vim.fn.writefile({ 'return {}' }, root .. '/src/plug/lua/plug.lua')
|
|
vim.fn.system({ 'tar', '-czf', root .. '/plug.tar.gz', '-C', root .. '/src', 'plug' })
|
|
|
|
local result = wait_extract(root .. '/plug.tar.gz', root .. '/dest')
|
|
assert_true(result.ok, tostring(result.err))
|
|
assert_true(fsmod.exists(root .. '/dest/plug/lua/plug.lua'))
|
|
assert_eq(root .. '/dest/plug', fsmod.strip_top_level(root .. '/dest'))
|
|
end)
|
|
|
|
it('rejects an unknown archive type', function()
|
|
local result = wait_extract(tmpdir() .. '/thing.rpm', tmpdir() .. '/dest')
|
|
assert_true(not result.ok)
|
|
assert_match('unsupported archive type', result.err)
|
|
end)
|
|
|
|
it('reports a corrupt tarball', function()
|
|
local root = tmpdir()
|
|
vim.fn.writefile({ 'definitely not a tarball' }, root .. '/bad.tar.gz')
|
|
local result = wait_extract(root .. '/bad.tar.gz', root .. '/dest')
|
|
assert_true(not result.ok)
|
|
assert_match('extraction failed', result.err)
|
|
end)
|
|
end)
|