The vimpack-ci ServiceAccount does not exist in argocd-apps, so every step failed at pod creation. Other non-Go repos in the estate use default.
vimpack
A vim.pack-shaped Neovim plugin manager that installs plugins from archives on an
artifact registry instead of git clones.
Why
Git-cloning plugins at startup makes an editor depend on GitHub being up, on git being
on PATH, and on whatever a moving branch happens to contain today. vimpack pulls a
tarball or zip at an explicit version from whatever HTTP registry you already run —
artifactapi, Artifactory, Nexus, S3 behind a CDN, a plain nginx directory — verifies it
against a checksum, and unpacks it into pack/vimpack/. The result is reproducible,
auditable, and works on hosts that cannot reach github.com.
Pure Lua. No dependencies beyond Neovim 0.10+ (vim.system, vim.uv, vim.json) plus
curl and tar/unzip for the fetch and unpack.
Install
vimpack manages plugins but does not manage itself. Drop it on your runtimepath — a
submodule, an archive from your registry, or a clone:
~/.config/nvim/pack/bootstrap/start/vimpack/
Configure
require('vimpack').setup({
registry = {
kind = 'artifactapi',
base_url = 'https://artifactapi.k8s.syd1.au.unkin.net',
repo = 'nvim-plugins',
auth = { env = 'ARTIFACTAPI_TOKEN' },
},
plugins = {
{ name = 'telescope.nvim', version = '0.1.8' },
{ name = 'oil.nvim', version = '2.11.0', sha256 = 'd9e2...' },
{ name = 'vim-fugitive', version = '3.7', opt = true },
-- explicit URL, registry bypassed entirely
{ name = 'plenary.nvim', version = '0.1.4', url = 'https://example.com/plenary-0.1.4.tar.gz' },
},
concurrency = 4,
timeout = 120000,
})
Then :VimpackInstall.
Plugin spec
| Field | Meaning |
|---|---|
name |
required; also the directory name under pack/vimpack/{start,opt}/ |
version |
required unless url is given; fed to the registry resolver |
sha256 |
optional; the download is rejected on mismatch |
opt |
true places the plugin in opt/ for :packadd |
url |
fully explicit archive URL; skips resolution |
ext |
archive extension, default tar.gz |
registry |
per-plugin registry override |
dir_name |
override the on-disk directory name |
Registries
A registry turns (name, version) into a download URL plus optional auth headers. A
resolver is a plain function:
--- (spec, registry) -> resolved | nil, err
--- resolved = { url = string, headers = table, filename = string, sha256 = string? }
require('vimpack').register_resolver('mine', function(spec, registry)
return {
url = registry.base_url .. '/' .. spec.name .. '@' .. spec.version .. '.tar.gz',
headers = {},
filename = spec.name .. '.tar.gz',
}
end)
Two resolvers ship built in.
template — any HTTP server
${name}, ${version}, ${ext}, ${slug}, ${repo} and ${base_url} are substituted.
An unknown placeholder is an error, not an empty string.
Artifactory generic repo:
registry = {
kind = 'template',
url_template = 'https://artifactory.example.com/artifactory/nvim-generic/${name}/${version}/${name}-${version}.${ext}',
auth = { header = 'X-JFrog-Art-Api', prefix = '', token_cmd = { 'pass', 'show', 'artifactory/api-key' } },
}
artifactapi — the estate's registry
Resolves to ${base_url}/api/v2/remotes/${repo}/files/${path}, where path defaults to
${name}/${name}-${version}.${ext} and is overridable via path_template.
registry = {
kind = 'artifactapi',
base_url = 'https://artifactapi.k8s.syd1.au.unkin.net',
repo = 'nvim-plugins',
auth = { env = 'ARTIFACTAPI_TOKEN' },
}
Auth
Tokens are never written into config. auth takes either:
env = 'VAR'— read from the environment, ortoken_cmd = { 'pass', 'show', 'path' }(or a shell string) — a command that prints the token.
header defaults to Authorization and prefix to Bearer for that header. A literal
token key is rejected at setup().
Commands
| Command | Effect |
|---|---|
:VimpackInstall [name...] |
install missing plugins and reconcile drift; up-to-date plugins are skipped |
:VimpackUpdate [name...] |
re-fetch regardless of the manifest |
:VimpackClean |
remove installed plugins no longer in the spec |
:VimpackStatus |
table of wanted vs installed version and state (ok, missing, drift, orphan) |
Downloads and extraction run through vim.system, capped at concurrency in flight, so
the UI stays responsive.
How it installs
- Resolve
(name, version)to a URL and headers. curlthe archive intostdpath('cache')/vimpack/archives/.- Verify
sha256when the spec carries one; a mismatch aborts with expected and got. - Extract into
stdpath('cache')/vimpack/stage/, hoisting a single wrapper directory. - Rename into
stdpath('data')/site/pack/vimpack/{start,opt}/<name>, parking the old tree aside first and restoring it if the rename fails. - Record name, version, sha256 and bucket in
stdpath('data')/vimpack/manifest.json.
A failure at any step leaves the previous install untouched.
Out of scope (v1)
Lazy loading. opt = true places a plugin under opt/ for :packadd; there is no
event, filetype or command trigger machinery.
Tests
nvim --headless --clean -l tests/minimal.lua
luacheck lua plugin tests
stylua --check lua plugin tests