Files
vimpack/doc/vimpack.txt
T
unkin-agent 35b9df9eba
ci/woodpecker/pr/pre-commit Pipeline failed
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline failed
Add vimpack: archive-based Neovim plugin manager
## 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.
2026-09-05 13:28:53 +10:00

173 lines
7.4 KiB
Plaintext

*vimpack.txt* Install Neovim plugins from archives on an artifact registry
==============================================================================
CONTENTS *vimpack-contents*
1. Introduction ............................. |vimpack-introduction|
2. Setup .................................... |vimpack-setup|
3. Plugin spec .............................. |vimpack-spec|
4. Registries ............................... |vimpack-registries|
5. Auth ..................................... |vimpack-auth|
6. Commands ................................. |vimpack-commands|
7. Layout ................................... |vimpack-layout|
8. Lua API .................................. |vimpack-api|
==============================================================================
1. INTRODUCTION *vimpack-introduction*
vimpack installs Neovim plugins from tarballs or zips served by an artifact
registry, at an explicit version, rather than by cloning git repositories.
Downloads are checksum-verified and swapped into place atomically, so a failed
install never leaves a half-written plugin behind.
Requirements: Neovim 0.10+, `curl`, and `tar` (plus `unzip` for .zip
archives).
==============================================================================
2. SETUP *vimpack-setup*
>lua
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 = 'vim-fugitive', version = '3.7', opt = true },
},
concurrency = 4,
timeout = 120000,
})
<
`setup()` validates the spec and returns the normalised config, or nil plus an
error message. Nothing is downloaded until |:VimpackInstall|.
Top-level options ~
registry default registry for every plugin (table, optional)
plugins list of plugin specs (list, default {})
concurrency maximum simultaneous downloads (number, default 4)
timeout per-download timeout in milliseconds (number, default 120000)
==============================================================================
3. PLUGIN SPEC *vimpack-spec*
name Plugin name. Required. Also the directory name under
pack/vimpack/{start,opt}/ unless dir_name overrides it.
version Required unless `url` is given. Passed to the resolver.
sha256 Expected digest of the archive. A mismatch aborts the install
and reports both the expected and the actual digest.
opt When true the plugin lands in opt/ for |:packadd|.
url Fully explicit archive URL; registry resolution is skipped.
ext Archive extension, default "tar.gz".
registry Per-plugin registry, overriding the top-level one.
dir_name On-disk directory name.
==============================================================================
4. REGISTRIES *vimpack-registries*
A registry maps (name, version) to a download URL. `kind` selects the
resolver; the default kind is "template".
template ~
Substitutes ${name}, ${version}, ${ext}, ${slug}, ${repo} and ${base_url} into
`url_template`. An unrecognised placeholder is reported as an error.
`filename_template` optionally names the cached archive.
>lua
registry = {
kind = 'template',
url_template = 'https://artifactory.example.com/artifactory/nvim-generic/'
.. '${name}/${version}/${name}-${version}.${ext}',
}
<
artifactapi ~
Builds ${base_url}/api/v2/remotes/${repo}/files/${path}. `path_template`
defaults to "${name}/${name}-${version}.${ext}".
>lua
registry = {
kind = 'artifactapi',
base_url = 'https://artifactapi.k8s.syd1.au.unkin.net',
repo = 'nvim-plugins',
}
<
custom ~
A resolver is `(spec, registry) -> resolved | nil, err`, where `resolved` is
`{ url, headers, filename, sha256? }`.
>lua
require('vimpack').register_resolver('mine', function(spec, registry)
return { url = registry.base_url .. '/' .. spec.name .. '-'
.. spec.version .. '.tar.gz', headers = {} }
end)
<
==============================================================================
5. AUTH *vimpack-auth*
Tokens are never stored in the config. `registry.auth` accepts:
env name of an environment variable holding the token
token_cmd command (list or shell string) that prints the token
header header name, default "Authorization"
prefix value prefix, default "Bearer " for Authorization
A literal `token` key is rejected at |vimpack-setup| time.
>lua
auth = { header = 'X-JFrog-Art-Api', prefix = '',
token_cmd = { 'pass', 'show', 'artifactory/api-key' } }
<
==============================================================================
6. COMMANDS *vimpack-commands*
*:VimpackInstall*
:VimpackInstall [{name}...]
Install plugins that are missing or whose installed version, digest or
start/opt placement has drifted from the spec. Up-to-date plugins are
skipped. With names, restrict the work to those plugins.
*:VimpackUpdate*
:VimpackUpdate [{name}...]
Re-fetch plugins whether or not the manifest says they are current.
*:VimpackClean*
:VimpackClean
Remove installed plugins and manifest entries that are no longer in the
spec.
*:VimpackStatus*
:VimpackStatus
Print wanted versus installed version per plugin, with a state of "ok",
"missing", "drift" or "orphan".
==============================================================================
7. LAYOUT *vimpack-layout*
stdpath('cache')/vimpack/archives/ downloaded archives
stdpath('cache')/vimpack/stage/ extraction staging area
stdpath('data')/vimpack/manifest.json installed name/version/sha256
stdpath('data')/site/pack/vimpack/start/<name>
stdpath('data')/site/pack/vimpack/opt/<name>
An install extracts to the staging area, hoists a single wrapper directory if
the archive has one, then renames into place after parking any previous tree
aside. If the rename fails the previous tree is restored.
Lazy loading is out of scope: `opt = true` only chooses the opt/ bucket.
==============================================================================
8. LUA API *vimpack-api*
require('vimpack').setup(opts) configure; returns cfg or nil,err
require('vimpack').install(names?) same as |:VimpackInstall|
require('vimpack').update(names?) same as |:VimpackUpdate|
require('vimpack').clean() same as |:VimpackClean|
require('vimpack').status() same as |:VimpackStatus|
require('vimpack').status_rows() status as a list of tables
require('vimpack').register_resolver(n, fn) add a registry resolver
vim:tw=78:ts=8:ft=help:norl: