Skip to content

Editor integration

@actup/lsp is a Language Server Protocol server. It is pure and offline — it never touches the network in the hot path. It provides, for uses: refs in workflow / composite-action files:

  • Diagnostics: floating refs (@main), non-semver tags, and a bare pinned SHA with no # <ref> comment.
  • Code action: unpin — rewrite a @<sha> # v4 pin back to @v4.
  • Hover: action identity, ref classification (semver / SHA / floating branch / non-semver), the offline advisory, and the recorded ref for a commented SHA pin.

The network "is this outdated / what is latest" check is intentionally not in the language server — that needs a tag cache + provider and lives in the actup CLI/engine.

The server only activates for files that are workflow YAML (**/.github/workflows/*.yml / *.yaml) or a composite action (action.yml / action.yaml).

Running the server

The @actup/lsp package is part of this workspace and is not published standalone. Run it directly with Bun:

bash
bun /path/to/actup-ts/packages/lsp/src/server.ts

It speaks LSP over stdio. A correctly framed initialize returns:

json
{ "serverInfo": { "name": "actup-lsp" }, "capabilities": { "textDocumentSync": 2, "codeActionProvider": true, "hoverProvider": true } }

If you do not want a Bun dependency, the VS Code extension ships a Node-bundled copy of the server (see below) — no Bun needed there.

VS Code

Use the bundled extension in packages/vscode-extension (it embeds both the client and a Node build of this server). See that package's README for build / install. No separate LSP setup is required.

Neovim

Neovim's built-in LSP client can start the server directly. Add to your config (Lua), adjusting the absolute path:

lua
local server = "/path/to/actup-ts/packages/lsp/src/server.ts"

vim.api.nvim_create_autocmd("FileType", {
  pattern = "yaml",
  callback = function(args)
    local name = vim.api.nvim_buf_get_name(args.buf)
    -- Only attach for workflow / action files (matches the server's own filter).
    if not (name:match("%.github/workflows/.*%.ya?ml$") or name:match("action%.ya?ml$")) then
      return
    end
    vim.lsp.start({
      name = "actup-lsp",
      cmd = { "bun", server },
      root_dir = vim.fs.root(args.buf, { ".git", ".github" }),
    })
  end,
})

Hover (K), code actions (vim.lsp.buf.code_action()) and diagnostics (vim.diagnostic.*) then work as usual. No nvim-lspconfig entry is needed, but you can wrap the same cmd in a custom configs definition if you prefer that workflow.

Zed

A Zed extension lives in editors/zed/ (a Rust crate compiled to WebAssembly). Like Neovim, it can't bundle the JS server, so it expects an actup-lsp command on PATH — a one-line wrapper:

sh
#!/bin/sh
exec bun /path/to/actup-ts/packages/lsp/src/server.ts "$@"

Make it executable and put it on your PATH (e.g. ~/.local/bin/actup-lsp).

Install the extension as a dev extension:

  1. cargo build --release --target wasm32-wasip2 in editors/zed/ (Zed also builds it for you on install).
  2. Zed → command palette → zed: install dev extension → select the editors/zed/ directory.

Zed then starts the actup-lsp server for YAML buffers; the server's own filter means only workflow / action.yml files get diagnostics, hover and the unpin/bump/pin quick-fixes.

Verified: the extension compiles cleanly to wasm32-wasip2 (zed_extension_api 0.7) — the structural contract Zed loads. It has not been run inside a live Zed, and it is not published to the Zed extension registry; treat the install steps as standard Zed dev- extension usage, not an actup guarantee.

What is verified

The server command above is verified to answer an LSP initialize over stdio with the capabilities shown, and the diagnostics / code action / hover behaviour is covered by the package's in-memory test suite. The Neovim snippet uses only stock vim.lsp APIs and the server's documented stdio contract; it has not been auto-tested inside a live Neovim, so treat the exact keymaps as your normal Neovim setup, not an actup guarantee.