@@ -10,6 +10,14 @@ The [package](https://www.npmjs.com/package/@actions/languageserver) contains Ty
1010npm install @actions/languageserver
1111```
1212
13+ To install the language server as a standalone CLI:
14+
15+ ``` bash
16+ npm install -g @actions/languageserver
17+ ```
18+
19+ This makes the ` actions-languageserver ` command available globally.
20+
1321## Usage
1422
1523### Basic usage using ` vscode-languageserver-node `
@@ -92,6 +100,150 @@ const clientOptions: LanguageClientOptions = {
92100const client = new LanguageClient (" actions-language" , " GitHub Actions Language Server" , serverOptions , clientOptions );
93101```
94102
103+ ### Standalone CLI
104+
105+ After installing globally, you can run the language server directly:
106+
107+ ``` bash
108+ actions-languageserver --stdio
109+ ```
110+
111+ This starts the language server using stdio transport, which is the standard way for editors to communicate with language servers.
112+
113+ ### In Neovim
114+
115+ #### 1. Install the language server
116+
117+ ``` bash
118+ npm install -g @actions/languageserver
119+ ```
120+
121+ #### 2. Set up filetype detection
122+
123+ Add this to your ` init.lua ` to detect GitHub Actions workflow files:
124+
125+ ``` lua
126+ vim .filetype .add ({
127+ pattern = {
128+ [" .*/%.github/workflows/.*%.ya?ml" ] = " yaml.ghactions" ,
129+ },
130+ })
131+ ```
132+
133+ This sets the filetype to ` yaml.ghactions ` for YAML files in ` .github/workflows/ ` , allowing you to keep separate YAML LSP configurations if needed.
134+
135+ #### 3. Create the LSP configuration
136+
137+ As of Neovim 0.11+ you can add this configuration in ` ~/.config/nvim/lsp/actionsls.lua ` :
138+
139+ ``` lua
140+ local function get_github_token ()
141+ local handle = io.popen (" gh auth token 2>/dev/null" )
142+ if not handle then return nil end
143+ local token = handle : read (" *a" ):gsub (" %s+" , " " )
144+ handle :close ()
145+ return token ~= " " and token or nil
146+ end
147+
148+ local function parse_github_remote (url )
149+ if not url or url == " " then return nil end
150+
151+ -- SSH format: git@github.com:owner/repo.git
152+ local owner , repo = url :match (" git@github%.com:([^/]+)/([^/%.]+)" )
153+ if owner and repo then
154+ return owner , repo : gsub (" %.git$" , " " )
155+ end
156+
157+ -- HTTPS format: https://github.com/owner/repo.git
158+ owner , repo = url :match (" github%.com/([^/]+)/([^/%.]+)" )
159+ if owner and repo then
160+ return owner , repo :gsub (" %.git$" , " " )
161+ end
162+
163+ return nil
164+ end
165+
166+ local function get_repo_info (owner , repo )
167+ local cmd = string.format (
168+ " gh repo view %s/%s --json id,owner --template '{{.id}}\t {{.owner.type}}' 2>/dev/null" ,
169+ owner ,
170+ repo
171+ )
172+ local handle = io.popen (cmd )
173+ if not handle then return nil end
174+ local result = handle : read (" *a" ):gsub (" %s+$" , " " )
175+ handle :close ()
176+
177+ local id , owner_type = result :match (" ^(%d+)\t (.+)$" )
178+ if id then
179+ return {
180+ id = tonumber (id ),
181+ organizationOwned = owner_type == " Organization" ,
182+ }
183+ end
184+ return nil
185+ end
186+
187+ local function get_repos_config ()
188+ local handle = io.popen (" git rev-parse --show-toplevel 2>/dev/null" )
189+ if not handle then return nil end
190+ local git_root = handle : read (" *a" ):gsub (" %s+" , " " )
191+ handle :close ()
192+
193+ if git_root == " " then return nil end
194+
195+ handle = io.popen (" git remote get-url origin 2>/dev/null" )
196+ if not handle then return nil end
197+ local remote_url = handle :read (" *a" ):gsub (" %s+" , " " )
198+ handle :close ()
199+
200+ local owner , name = parse_github_remote (remote_url )
201+ if not owner or not name then return nil end
202+
203+ local info = get_repo_info (owner , name )
204+
205+ return {
206+ {
207+ id = info and info .id or 0 ,
208+ owner = owner ,
209+ name = name ,
210+ organizationOwned = info and info .organizationOwned or false ,
211+ workspaceUri = " file://" .. git_root ,
212+ },
213+ }
214+ end
215+
216+ return {
217+ cmd = { " actions-languageserver" , " --stdio" },
218+ filetypes = { " yaml.ghactions" },
219+ root_markers = { " .git" },
220+ init_options = {
221+ -- Optional: provide a GitHub token and repo context for added functionality
222+ -- (e.g., repository-specific completions)
223+ sessionToken = get_github_token (),
224+ repos = get_repos_config (),
225+ },
226+ }
227+ ```
228+
229+ #### 4. Enable the LSP
230+
231+ Add to your ` init.lua ` :
232+
233+ ``` lua
234+ vim .lsp .enable (' actionsls' )
235+ ```
236+
237+ #### 5. Verify it's working
238+
239+ Open any ` .github/workflows/*.yml ` file and run:
240+
241+ ``` vim
242+ :checkhealth vim.lsp
243+ ```
244+
245+ You should see ` actionsls ` in the list of attached clients.
246+
95247## Contributing
96248
97249See [ CONTRIBUTING.md] ( ../CONTRIBUTING.md ) at the root of the repository for general guidelines and recommendations.
@@ -110,6 +262,27 @@ or to watch for changes
110262npm run watch
111263```
112264
265+ ### Running the language server locally
266+
267+ After running
268+
269+ ``` bash
270+ npm run build:cli
271+ npm link
272+ ```
273+
274+ ` actions-languageserver ` will be available globally. You can start it with:
275+
276+ ``` bash
277+ actions-languageserver --stdio
278+ ```
279+
280+ Once linked you can also watch for changes and rebuild automatically:
281+
282+ ``` bash
283+ npm run watch:cli
284+ ```
285+
113286### Test
114287
115288``` bash
0 commit comments