In this post, I will show you how to create dynamic task runners for your npm projects in Emacs. This is a simple example of how to use Emacs We are going to use prodigy for this example
Usually inside an npm project we run the tasks from the package.json file from the scripts
section. A typical package.json file will look like the following
{
"name": "alacritty-themes",
"version": "5.3.1",
"description": "Themes for Alacritty : A cross-platform GPU-Accelerated Terminal emulator",
"main": "index.js",
"bin": {
"alacritty-themes": "./bin/cli.js"
},
"scripts": {
"commit": "cz",
"deploy": "git push && git push --tags && npm publish",
"lint": "eslint .",
"semantic-release": "semantic-release",
"test": "mocha --recursive"
}
}
So if you want to run the lint task you will run the following in your shell:
npm run lint
If you are inside Emacs, you probably need to open a new terminal or eshell
buffer and manually type the above command. Also if want to run another command in parallel
you need to open a separate buffer and run the command.
So this involves a lot of manual work and it is not a very efficient way to do it. What if we can create a task runner that will run the tasks in parallel? What if we can do that in a way that will be dynamic? That is exactly what we are going to do here using a prodigy service.
Prodigy
What is prodigy? Prodigy is a framework for creating Emacs-based task runners. You can use it to manage external services from within Emacs. You can find more details in the prodigy documentation
To create new prodigy services you need to call the prodigy-define-service
function
(prodigy-define-service
:command (lambda (&rest args)
(let ((service (plist-get args :service)))
;; ...
)))
Now, let us see how we can achieve this using prodigy in Emacs
(require 'prodigy)
(defun my/create-prodigy-service (&optional package-manager)
"Create new prodigy services based on current package.json"
(interactive)
(let ((pkg (json-parse-string (buffer-substring-no-properties (point-min) (point-max)))))
(maphash (lambda (key value)
(let ((args '())
(name (gethash "name" pkg)))
(add-to-list 'args key)
(add-to-list 'args "run")
(prodigy-define-service
:name (concat name "-" key)
:command (or package-manager "npm")
:cwd (file-name-directory (buffer-file-name))
:path (file-name-directory (buffer-file-name))
:args args
:tags '(temp)
:stop-signal 'sigkill
:kill-process-buffer-on-stop t
))) (gethash "scripts" pkg))
(prodigy)
(prodigy-refresh)))
This code is defining a function called my/create-prodigy-service
that creates new prodigy services based on the contents of the current package.json
file.
You can put this in your init.el file and call it from within Emacs.
Here is a breakdown of the code:
The (interactive)
line is important if you plan to use the function interactively, meaning you can call it through a keybinding or by typing M-x
and the function name.
In this line, the contents of the current buffer are parsed as a JSON string using the json-parse-string
function.
The result is stored in a variable called pkg
. The (point-min)
and (point-max)
functions are used to get the positions of the first and last characters of the buffer, so that buffer-substring-no-properties
can extract the contents of the buffer as a string.
Then we use the maphash
function to iterate over each key-value pair in the "scripts"
hash in the pkg
variable. The gethash
function is used to get the value associated with the "scripts"
key.
And next we define an anonymous function that takes two arguments: key
and value
. This function will be used as the mapping function for maphash
.
Inside the mapping function, a new variable args
is initialized as an empty list. Another variable name
is assigned the value of the "name"
key in the pkg
variable.
Then we define a new prodigy service using the prodigy-define-service
function. The :name
parameter uses the concat
function to combine the name
and key
variables. The :command
parameter is set to "npm". The :cwd
and :path
parameters are set to the directory of the current buffer file. The :args
parameter is set to the args
list. The :tags
parameter is set to '(temp), indicating that the service is temporary. The :stop-signal
parameter is set to sigkill
to force-stop the service. The :kill-process-buffer-on-stop
parameter is set to t
to automatically kill the process buffer when the service is stopped.
Finally, prodigy
is called to start all defined prodigy services and then prodigy-refresh
to refresh the list of services.
Overall, this code creates prodigy services for each script defined in the "scripts"
section of the package.json
file. It uses the name of the package as part of the service name, and sets the appropriate command, working directory, and arguments for each service.
Here is how it works, open your package.json file situated in the project root folder.
And invoke M-x my/create-prodigy-service
If you want to call inside a lisp file, you can define a function and call it using the following code:
(my/create-prodigy-service)
By default it will use npm
as the default package manager, if you want to use any other package manager, you can specify it as follows:
For using yarn:
(my/create-prodigy-service "yarn")
For using pnpm:
(my/create-prodigy-service "pnpm")
Hope you enjoyed the post, if you have any alternate approaches, feedback or queries, please let me know in the comments section.
Top comments (1)
This looks super handy! I don't know how I hadn't heard of this. Thank you for sharing it!