Logo

dev-resources.site

for different kinds of informations.

Creating dynamic task runners for your npm scripts in Emacs

Published at
1/3/2024
Categories
productivity
emacs
programming
Author
rajasegar
Categories
3 categories in total
productivity
open
emacs
open
programming
open
Author
9 person written this
rajasegar
open
Creating dynamic task runners for your npm scripts in Emacs

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"
        }
    }
Enter fullscreen mode Exit fullscreen mode

So if you want to run the lint task you will run the following in your shell:

    npm run lint
Enter fullscreen mode Exit fullscreen mode

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)))
                 ;; ...
                 )))
Enter fullscreen mode Exit fullscreen mode

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)))
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

For using pnpm:

    (my/create-prodigy-service "pnpm")
Enter fullscreen mode Exit fullscreen mode

Image description

Hope you enjoyed the post, if you have any alternate approaches, feedback or queries, please let me know in the comments section.

emacs Article's
30 articles in total
Favicon
Emacs 2024 Changes
Favicon
emacs error Failed to verify signature archive-contents.sig
Favicon
Mastering Parentheses in Emacs: Essential Commands
Favicon
C Development with GNU Emacs
Favicon
Mastering Golang Debugging in Emacs
Favicon
Emacs for Python and Poetry Using `basedpyright-langserver`
Favicon
Explorando org-babel en emacs
Favicon
(Game)Dev with Emacs - Because it's not Already Hard Enough Without it
Favicon
The Power of Tries, Data Structure Optimization in Emacs
Favicon
Creating an Emacs major mode - because why not?
Favicon
Learning to Like Neovim
Favicon
Emacs, a simple tour
Favicon
Learning Lisp
Favicon
Managing multiple terminals in Emacs
Favicon
Vim-style repeatable key bindings for navigating windows in Emacs
Favicon
Mermaid preview using xwidget browser
Favicon
Setting up Doom Emacs for Astro Development
Favicon
Moving to Emacs Tree Sitter Modes
Favicon
Mastering JSX Editing in Emacs with Tree-sitter
Favicon
Exploring Syntax Trees in Emacs with Tree-sitter
Favicon
Chinese Zodiac Time for Emacs
Favicon
Using Jmespath in Emacs
Favicon
From Doom to Vanilla Emacs
Favicon
Tell runtime/cgo to not thread warnings as errors
Favicon
Creating dynamic task runners for your npm scripts in Emacs
Favicon
5 ways to get text from an Emacs buffer
Favicon
Using age with org-journal
Favicon
Once again I wanted to have Emacs be my XML Notepad 2006
Favicon
Dia 9
Favicon
Emacs is More Like a Terminal Than an Editor

Featured ones: