# actions/languageserver `actions-languageserver` hosts the `actions-languageservice` and makes it available via the [language server protocol](https://microsoft.github.io/language-server-protocol/) (LSP) as a standalone language server. ## Installation The [package](https://www.npmjs.com/package/@actions/languageserver) contains TypeScript types and compiled ECMAScript modules. ```bash npm install @actions/languageserver ``` To install the language server as a standalone CLI: ```bash npm install -g @actions/languageserver ``` This makes the `actions-languageserver` command available globally. ## Usage ### Basic usage using `vscode-languageserver-node` For the server, import the module. It detects whether it's running in a Node.js environment or a web worker and initializes the appropriate connection. `server.ts`: ```typescript import "@actions/languageserver"; ``` For the client, create a new `LanguageClient` pointing to the server module. `client.ts`: ```typescript import {LanguageClient, ServerOptions, TransportKind} from "vscode-languageclient/node"; const debugOptions = {execArgv: ["--nolazy", "--inspect=6010"]}; const clientOptions: LanguageClientOptions = { documentSelector: [{ pattern: "**/.github/workflows/*.{yaml,yml}" }] }; const serverModule = context.asAbsolutePath(path.join("dist", "server.js")); const serverOptions: ServerOptions = { run: {module: serverModule, transport: TransportKind.ipc}, debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } }; const client = new LanguageClient("actions-language", "GitHub Actions Language Server", serverOptions, clientOptions); ``` ### From a web worker See [../browser-playground](../browser-playground) for an example implementation that hosts the language server in a web worker. ### Providing advanced functionality The language server accepts initialization options that can be used to configure additional functionality. If you pass in a github.com `sessionToken`, the language service will use data from github.com to perform additional validations and provide additional auto-completion suggestions. ```typescript export interface InitializationOptions { /** * GitHub token that will be used to retrieve additional information from github.com * * Requires the `repo` and `workflow` scopes */ sessionToken?: string; /** * List of repositories that the language server should be aware of */ repos?: RepositoryContext[]; /** * Desired log level */ logLevel?: LogLevel; /** * Experimental features that are opt-in */ experimentalFeatures?: ExperimentalFeatures; } ``` pass the `initializationOptions` to the `LanguageClient` when establishing the connection: ```typescript const clientOptions: LanguageClientOptions = { documentSelector: [{ pattern: "**/.github/workflows/*.{yaml,yml}" }], initializationOptions: initializationOptions }; const client = new LanguageClient("actions-language", "GitHub Actions Language Server", serverOptions, clientOptions); ``` ### Experimental Features The language server supports opt-in experimental features via the `experimentalFeatures` initialization option. These features may change or be removed in between releases. ```typescript initializationOptions: { experimentalFeatures: { // Enable all experimental features all: true, // Or enable specific features missingInputsQuickfix: true, } } ``` **Available experimental features:** | Feature | Description | |---------|-------------| | `missingInputsQuickfix` | Code action to add missing required inputs for actions | | `blockScalarChompingWarning` | Warn when block scalars (`\|` or `>`) use implicit clip chomping, which adds a trailing newline that may be unintentional | | `allowConcurrencyQueue` | Enable the `concurrency.queue` workflow property | Individual feature flags take precedence over `all`. For example, `{ all: true, missingInputsQuickfix: false }` enables all experimental features except `missingInputsQuickfix`. When a feature graduates to stable, its flag becomes a no-op and the feature will be enabled regardless of the configuration value. ### Standalone CLI After installing globally, you can run the language server directly: ```bash actions-languageserver --stdio ``` This starts the language server using stdio transport, which is the standard way for editors to communicate with language servers. ### In Neovim #### 1. Install the language server ```bash npm install -g @actions/languageserver ``` #### 2. Set up filetype detection Add this to your `init.lua` to detect GitHub Actions workflow files: ```lua vim.filetype.add({ pattern = { [".*/%.github/workflows/.*%.ya?ml"] = "yaml.ghactions", }, }) ``` This sets the filetype to `yaml.ghactions` for YAML files in `.github/workflows/`, allowing you to keep separate YAML LSP configurations if needed. #### 3. Create the LSP configuration As of Neovim 0.11+ you can add this configuration in `~/.config/nvim/lsp/actionsls.lua`: ```lua local function get_github_token() local handle = io.popen("gh auth token 2>/dev/null") if not handle then return nil end local token = handle: read("*a"):gsub("%s+", "") handle:close() return token ~= "" and token or nil end local function parse_github_remote(url) if not url or url == "" then return nil end -- SSH format: git@github.com:owner/repo.git local owner, repo = url:match("git@github%.com:([^/]+)/([^/%.]+)") if owner and repo then return owner, repo: gsub("%.git$", "") end -- HTTPS format: https://github.com/owner/repo.git owner, repo = url:match("github%.com/([^/]+)/([^/%.]+)") if owner and repo then return owner, repo:gsub("%.git$", "") end return nil end local function get_repo_info(owner, repo) local cmd = string.format( "gh repo view %s/%s --json id,owner --template '{{.id}}\t{{.owner.type}}' 2>/dev/null", owner, repo ) local handle = io.popen(cmd) if not handle then return nil end local result = handle: read("*a"):gsub("%s+$", "") handle:close() local id, owner_type = result:match("^(%d+)\t(.+)$") if id then return { id = tonumber(id), organizationOwned = owner_type == "Organization", } end return nil end local function get_repos_config() local handle = io.popen("git rev-parse --show-toplevel 2>/dev/null") if not handle then return nil end local git_root = handle: read("*a"):gsub("%s+", "") handle:close() if git_root == "" then return nil end handle = io.popen("git remote get-url origin 2>/dev/null") if not handle then return nil end local remote_url = handle:read("*a"):gsub("%s+", "") handle:close() local owner, name = parse_github_remote(remote_url) if not owner or not name then return nil end local info = get_repo_info(owner, name) return { { id = info and info.id or 0, owner = owner, name = name, organizationOwned = info and info.organizationOwned or false, workspaceUri = "file://" .. git_root, }, } end return { cmd = { "actions-languageserver", "--stdio" }, filetypes = { "yaml.ghactions" }, root_markers = { ".git" }, init_options = { -- Optional: provide a GitHub token and repo context for added functionality -- (e.g., repository-specific completions) sessionToken = get_github_token(), repos = get_repos_config(), }, } ``` #### 4. Enable the LSP Add to your `init.lua`: ```lua vim.lsp.enable('actionsls') ``` #### 5. Verify it's working Open any `.github/workflows/*.yml` file and run: ```vim :checkhealth vim.lsp ``` You should see `actionsls` in the list of attached clients. ## Contributing See [CONTRIBUTING.md](../CONTRIBUTING.md) at the root of the repository for general guidelines and recommendations. If you do want to contribute, please run [prettier](https://prettier.io/) to format your code and add unit tests as appropriate before submitting your PR. ### Build ```bash npm run build ``` or to watch for changes ```bash npm run watch ``` ### Running the language server locally After running ```bash npm run build:cli npm link ``` `actions-languageserver` will be available globally. You can start it with: ```bash actions-languageserver --stdio ``` Once linked you can also watch for changes and rebuild automatically: ```bash npm run watch:cli ``` ### Test ```bash npm test ``` or to watch for changes and run tests: ```bash npm run test-watch ``` ### Lint ```bash npm run format-check ``` ## License This project is licensed under the terms of the MIT open source license. Please refer to [MIT](../LICENSE) for the full terms.