A registry with a stray space or newline passed validation and was concatenated verbatim into the archive URL, so curl saw the malformed address instead of the user seeing the required-registry error. Trim the value before matching and reject any whitespace left inside it; apply the same to a spec's explicit url.
arti-pack
A Neovim plugin manager that installs plugins from an artifact repository
instead of git. It downloads tagged upstream source archives over HTTP and
unpacks them into Neovim's native package path — see :h packages.
It works with any HTTP artifact repository that serves an upstream tag archive at a stable URL — a JFrog Artifactory remote GitHub repository, or any caching proxy with the same path shape. Nothing is cloned, no git binary is needed, and the editor never has to reach GitHub: every plugin arrives as a cached, immutable, versioned artifact from the same store your builds already pull from.
Requirements
- Neovim 0.10+
curl, andunzip(ortarwhen usingext = 'tar.gz')
Install
arti-pack manages itself least awkwardly as a plain clone into the package path:
git clone https://git.unkin.net/unkin/arti-pack \
"${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack/arti-pack/start/arti-pack"
Usage
require('arti-pack').setup({
registry = 'https://artifacts.example.com/artifactory/github-remote',
plugins = {
{ 'nvim-lua/plenary.nvim', tag = 'v0.1.4' },
{ 'folke/which-key.nvim', tag = 'v3.13.3', opt = true },
{ 'folke/tokyonight.nvim', tag = 'v4.10.0', as = 'tokyonight' },
{ 'tpope/vim-fugitive', tag = 'v3.7', ext = 'tar.gz' },
},
})
registry is required and has no default: it is the URL prefix your own
artifact repository serves upstream tag archives under, so there is no sensible
value to ship. It must be an http:// or https:// URL; surrounding
whitespace is trimmed and anything else is rejected. Until you set it,
:ArtiPackSync refuses to run and says so:
[arti-pack] registry is not configured; set it in require('arti-pack').setup{ registry = 'https://artifacts.example.com/artifactory/github-remote' }
:ArtiPackClean and :ArtiPackList never resolve a URL, so they work without
it, as do plugins that carry an explicit url.
Every plugin needs an explicit tag — that is the point. Plugins install to
stdpath('data')/site/pack/arti-pack/start/<name>, or .../opt/<name> when
opt = true.
Spec fields
| Field | Default | Meaning |
|---|---|---|
[1] |
required | owner/repo |
tag |
required | Upstream tag, used verbatim in the archive path |
opt |
false |
Install under opt/ for :packadd instead of start/ |
as |
repo name | Directory name to install as |
ext |
'zip' |
zip or tar.gz |
url |
— | Full archive URL, bypassing the registry (no whitespace) |
Options
| Option | Default | Meaning |
|---|---|---|
registry |
required | Archive URL prefix placed in front of owner/repo |
auto_sync |
true |
Sync missing and changed plugins on startup |
concurrency |
4 |
Plugins downloaded in parallel |
timeout |
120 |
Per-download timeout in seconds |
Commands
| Command | Effect |
|---|---|
:ArtiPackSync |
Install missing plugins and update ones whose tag changed |
:ArtiPackSync! |
Reinstall everything, ignoring recorded state |
:ArtiPackClean |
Remove installed plugins no longer in the spec |
:ArtiPackList |
Show every configured plugin and its state |
How it resolves an archive
<registry>/<owner>/<repo>/archive/refs/tags/<tag>.<ext>
which for the example above is
https://artifacts.example.com/artifactory/github-remote/nvim-lua/plenary.nvim/archive/refs/tags/v0.1.4.zip
That is GitHub's own tag-archive path shape, so a remote repository proxying
github.com resolves it unchanged.
Archives are cached under stdpath('cache')/arti-pack, checked for a real zip
or gzip magic header before being trusted, and unpacked into a staging
directory. Tag archives wrap their contents in a single <repo>-<version>
directory, which is stripped on the way in. Installed tags are recorded in
stdpath('data')/arti-pack/lock.json, so a sync with nothing to do costs
nothing. Helptags are regenerated per plugin after install.
A failed plugin is reported and skipped; the rest of the run continues.
Tests
make test