DEV Community

Cover image for Launching “$ npm run” programmatically with `npm.run()`
Stefano Magni
Stefano Magni

Posted on

Launching “$ npm run” programmatically with `npm.run()`

No need for child_process.exec() etc. Pre and post scripts are respected too.

The problem of mine

While working on the cypress-wait-until library, I’d like to upload the test videos on the Cypress Dashboard. Doing that is really simple, I only had to update my package.json dedicated script:

  {
    "scripts": {
      // from
      "cy:run": "cypress run"

      // to
      "cy:run": "cypress run --record --key YOUR_SECRET_KEY"
    }
  }
Enter fullscreen mode Exit fullscreen mode

But I had to face a big problem: the YOUR_SECRET_KEY environment variable was going to be available just in the Travis build, not locally. And if you run it on your local machine, without a defined YOUR_SECRET_KEY, the cypress run command failed.

There are a lot of solutions to the above problem (like child_process.exec)but all of them come with a cost and some effects to be managed. More: I needed to change the way I manage the package.json scripts too.

I’d like to have a transparent solution that allowed me to make the YOUR_SECRET_KEY optional but without changing anything in my scripts management.

Npm as a local dependency

While googling I found an interesting solution: installing Npm locally and leveraging its APIs. How it works:

  • first of all, install npm with

    $ npm install --save-dev npm

  • then, create a .js file

    $ touch index.js && open index.js

  • import npm

    const npm = require("npm");
    
  • let npm loading the current project

    npm.load();
    
  • pass a callback to the npm.load() API and run the script of your

    npm.load(() *=>* npm.run("SCRIPT_NAME"));
    

Some notes about the npm.run() API:

  • you cannot run it without calling npm.load in advance. If you try to do that you get a "Error: Call npm.load(config, cb) before using this command." error

  • the first parameter is the script name, if you need to pass some options you must pass an array of strings

    npm.run("test param"); // Error: missing script: test param
    
    npm.run("test", "param"); // "param" is passed to the "test" script
    
  • pre and post package.json scripts are launched too

I shared a npm-run-programmatically-example repository where you play with it, here a recorded gif of a terminal session of the repository:

The terminal session that shows the result of npm.run()You can watch the same video on asciicinema too.

How this solution fitted my needs

I solved the original problem this way:

  • I added one more cy:run-uploading-videos script to be launched instead of cy:run
    {
      "scripts": {
        "cy:run-uploading-videos": "node cypress-run.js",
        "cy:run": "cypress run"
      }
    }
Enter fullscreen mode Exit fullscreen mode
  • the cypress-run.js file looked like this
    const npm = require('npm');
    npm.load(() => {
      const key = process.env.CYPRESS_RECORD_KEY;
      const options = key ? ['--record', '--key', key] : [];
      npm.run('cy:run', ...options);
    });
Enter fullscreen mode Exit fullscreen mode

So I could launch the usual cy:run script locally and launching cy:run-uploading-videos on Travis 🎉 (without duplicating the cypress run call that could have more parameters in the future).

What could I do with npm.run()?

An idea of mine is nprr, a tool that enhances npm run with autocomplete. Have you ever felt the situation when you do not remember the name of the script you want to run? So you open the package.json file, look for the script name and then run it… Well, nprr solves this problem! Watch it in action:

Nprr in action, it enhances the standard "npm run" with autocomplete.You can watch the same video on asciicinema too.

The possibilities are endless and if you leverage npm programmatically, please leave a comment with your experiments/packages/utilities, etc. 😊

Discussion (0)