unkin-agent 5809a843c8
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Extract archives in pure Lua
Installing a plugin no longer needs unzip or tar on PATH, and entry paths
are vetted in Lua instead of trusting an external tool.

Add arti-pack.archive: a zip reader over zzlib's inflate and a tar reader
over its gunzip.
Reject absolute, parent-walking, backslashed and NUL-bearing entry names,
and any entry that is not a regular file or directory.
Verify every zip entry's CRC32 and size, and fail the install on mismatch.
Carry only the executable bit from archive modes.
Replace fs.extract_cmd with fs.extract and call it synchronously.
Drop unzip from the test pipeline and the stated requirements.
2026-09-10 22:48:25 +10:00
2026-09-10 22:48:25 +10:00
2026-09-10 22:48:25 +10:00
2026-09-10 22:48:25 +10:00
2026-09-10 22:40:41 +10:00
2026-09-10 22:48:25 +10:00

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

Archives are unpacked in pure Lua, so no unzip or tar binary is needed. Entry paths are validated and every entry's CRC32 is checked before it lands on disk.

Install

Each tag is published as a source zip under the same tag-archive path shape arti-pack expects, so it installs the same way as everything it manages — mirror it into your own artifact repository and fetch it from there:

curl -fsSL -o /tmp/arti-pack.zip \
  https://artifacts.example.com/artifactory/nvim-plugins/unkin/arti-pack/archive/refs/tags/v0.1.0.zip
mkdir -p "${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack/arti-pack/start"
unzip -q /tmp/arti-pack.zip -d /tmp/arti-pack
mv /tmp/arti-pack/arti-pack-0.1.0 \
  "${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack/arti-pack/start/arti-pack"

Once installed it can carry itself, since url bypasses the registry:

{
  'unkin/arti-pack',
  tag = 'v0.1.0',
  url = 'https://artifacts.example.com/artifactory/nvim-plugins/unkin/arti-pack/archive/refs/tags/v0.1.0.zip',
}

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
S
Description
Neovim plugin manager that installs plugins from tagged archives in an HTTP artifact repository into nvim's native package path
Readme 256 KiB
Languages
Lua 97.7%
Makefile 2.3%