Validation & CI
Blak has three safety nets every contributor runs locally and CI runs on every push:
- Static validation — no Neovim required, runs in seconds.
- Smoke test — boots Neovim headless and asserts setup works end to end.
- Installer smoke test — runs the public installer into temporary XDG directories and boots the sparse runtime checkout.
make validate
Runs scripts/validate.py.
It also syntax-checks install.sh and scripts/smoke-install.sh.
What it checks
| Check | Why |
|---|---|
| Lua syntax: balanced delimiters | Catch { without }, etc., in 0 ms — no Lua runtime required. |
| Lua syntax: keyword nesting | if / function / for / while / repeat close with end (or until for repeat). |
| Require paths | Every require("blak.…") resolves to an actual file. |
| Unique extra IDs | No two files under lua/blak/extras/ declare the same id. |
| Plugin loading | Default and extra plugin specs declare cmd, event, ft, keys, lazy = true, or an approved eager-startup reason. |
| Splash frame structure | Frame count and color count match. Each frame is 14 rows of 50 braille glyphs. Color spans are well-formed. |
| Required docs | README.md, CONTRIBUTING.md, NOTICE, doc/blak.txt, doc/blak-extras.txt, doc/blak-keymaps.txt, .github/workflows/ci.yml exist. |
| Legacy identifier cleanup | No leftover "black" references from the early-naming migration. |
Output
Validation passed: 85 Lua files, 36 extrasOn failure, every problem is reported in one pass — fix them in a batch rather than running validate → fix → run → fix.
When to run
- Before committing.
- Before opening a PR.
- In a tight loop while refactoring (it’s milliseconds).
make smoke
Runs Neovim headless against this checkout four times:
- First invocation: sets the runtime path, runs
:Lazy! syncto install plugins, then quits. - Second invocation: cold-boot test — sets the runtime path, executes
scripts/smoke.lua, then quits. - Third invocation: command-contract test — executes
scripts/commands.lua, exercises every public:Blakcommand, stubs destructive Lazy actions, verifies extras state changes, and confirms command completion. - Fourth invocation: starts with
.as the directory argument and checksscripts/smoke-directory.lua, soblak .cannot regress to an empty buffer.
What smoke.lua does
vim.g.blak_config = { ui = { splash = { enabled = false } }, mason = { automatic_install = false },}require("blak").setup()assert(require("blak.config").get() ~= nil, "config missing")assert(vim.fn.exists(":Lazy") == 2, ":Lazy missing")assert(vim.fn.exists(":BlakTerminal") == 2, ":BlakTerminal missing")assert(vim.fn.maparg("<leader>/", "n", false, true).desc == "Grep")assert(vim.fn.maparg("<leader>tt", "n", false, true).desc == "Terminal")assert(vim.fn.maparg("<leader>le", "n", false, true).desc == "Extras")assert(vim.fn.maparg("-", "n") == "")assert(require("blak.config").get().explorer.provider == "oil")require("blak.core.explorer").open({ explorer = { provider = "snacks" } })local lazy_plugins = require("lazy.core.config").pluginsassert(lazy_plugins["oil.nvim"].lazy == false)assert(lazy_plugins["oil.nvim"].opts.default_file_explorer == true)assert(lazy_plugins["fff"].lazy == true)assert(lazy_plugins["nvim-treesitter"].lazy == true)assert(lazy_plugins["nvim-lspconfig"].lazy == true)vim.cmd("checkhealth blak")Translation:
- Disable the splash and Mason auto-install (both noisy in headless).
- Run setup. Any validation error or runtime error fails the test.
- Confirm the merged config exists.
- Confirm config merging avoids full runtime-path scans and startup only locates
lua/blak/user.luaonce for reload watching. - Confirm lazy.nvim’s
:Lazycommand is registered. - Confirm Blak’s terminal command and core keymaps are registered.
- Confirm the explorer dispatcher defaults to Oil and can target Snacks.
- Confirm Oil is eager and owns directory buffers.
- Confirm file/picker/LSP-heavy defaults defer to lazy triggers.
- Run
:checkhealth blak— any error or warning in the health module shows up in output.
What commands.lua does
The command-contract smoke test asserts every documented :Blak* command exists and can be invoked. It exercises overview, health, keys, news, picker dispatch, extras list/enable/disable/sync, update/upgrade/rollback snapshot and migration wiring, tool/parser install no-op paths, formatting, format toggles, terminal, and splash preview.
The update-contract smoke test focuses only on the update trust model: rollback snapshots include lockfile and config state, :BlakUpdate blocks channel changes and pending breaking migrations, :BlakUpgrade applies migrations and accepts the intended channel, :BlakRollback restores every tracked file, automatic LazyUpdatePre snapshots are de-duplicated, and legacy lockfile backups still restore.
It stubs Lazy’s update/sync/restore commands so CI checks Blak’s command behavior without doing network updates in the middle of the test.
Smoke needs Neovim 0.12+
make smokeUses NVIM_APPNAME=blak-test (configurable: SMOKE_NVIM_APPNAME=foo make smoke). The state dir is at $XDG_DATA_HOME/blak-test/ — feel free to wipe between runs.
make smoke-install
Runs scripts/smoke-install.sh.
The script creates temporary XDG directories, runs install.sh against the local checkout, asserts that the install contains runtime files and omits contributor-only directories, then starts Neovim with NVIM_APPNAME=blak-install-smoke.
It specifically checks that docs/, scripts/, .github/, and assets/blackhole.gif do not land in the installed checkout, while init.lua, lua/blak/, doc/, lazy-lock.json, NEWS.md, .gitignore, .ignore, and assets/blak-ascii.svg do.
CI
.github/workflows/ci.yml runs these checks on every push and PR.
| Job | Runner | Step |
|---|---|---|
validate | ubuntu-latest | Run make validate |
smoke | ubuntu-latest | Install Neovim stable, run make smoke and make smoke-install |
.github/workflows/docs.yml builds and deploys this documentation site to getblak.dev via GitHub Pages on every push to main.
Makefile reference
make validate # static validator + shell syntax checksmake smoke # headless Neovim + Lazy sync + checkhealthmake smoke-install # install.sh into temp XDG dirs + headless bootmake docs # alias for docs-devmake docs-install # cd docs && npm installmake docs-dev # cd docs && npm run dev (http://localhost:4321/)make docs-build # cd docs && npm run buildmake zip # zip the repo for distribution (excludes git, node_modules, dist)Pre-commit checklist
make validate # < 100 msmake smoke # ~10 s (longer on cold lazy cache)make smoke-install # verifies the public install pathstylua --check . # when stylua is installed; CI enforces it eventuallyIf all three pass locally, CI will pass.