0.2.1 • Public • Published

    run – Simple development task automation


    run is a simple, enhanced replacement for npm run-script <command> [-- <args>...] with zero third-party dependencies. If you already use npm run, you can use run immediately. But run can do a bit more as well.


    npm install -g run-simple


    Command Line

    run [--version] [-h | --help] [-q | --quiet | -s | --silent] <command> [-- <args>...]


    const Runner = require('run-simple')
    const runner = new Runner()
    runner.on('error', (error) => {
    runner.on('exit', (code) => {
    runner.on('list', (scripts) => {
      console.log(`${runner.appName}${runner.moduleRoot} scripts:`)
      Object.keys(scripts).forEach((name) => console.log(`  ${name}:\n    ${scripts[name]}`))
    runner.on('run', (spec) => {
      console.log(`${}${spec.version ? `@${spec.version}` : ''} ${spec.wd}`)

    Why not npm run-script?

    Obviously, npm is a fine piece of software. And npm run-script (AKA npm run) is one of the simplest development automation tools available. I love it, and as a result, I have never wanted to waste time learning more featureful alternatives like gulp or grunt.

    But… it could be better. One thing that npm run doesn’t support well is signal handling. When npm run executes scripts, handling signals like 'SIGINT' within the scripts themselves is unreliable at best, which means a script that should clean up before exiting simply can’t. The implications go beyond signal handling – scripts behave differently when npm runs them than they do when they run by themselves. npm run just isn’t satisfied to run something and then get out out of the way.

    Which may explain why npm run is verbose to a fault. npm has a lot more on its mind than just running your scripts so by default you’re going to see more output than you really need. Sure, you can pass it --silent or redirect output to /dev/null, but other people (your teammates and mine, for example) will need to do the same or they’ll get a bunch of npm disclaimer boilerplate when what they really want to see is what the script itself did.

    Finally, package.json is great for project configuration, but it’s a pretty poor place to write scripts. There’s no way to comment or document your scripts. And being forced to write everything on a single line either hinders readability or forces artificial factoring of script logic.

    Why run?

    Although run is quieter and will cede more control to scripts, it is designed to work as much like npm run as possible. In fact, you can easily use it as if it was nothing more than an alias for npm run; it’ll happily find and execute all the scripts you have already defined in package.json.

    But if you create a scripts.js file, run will look there for tasks as well. Here’s what the run project’s own scripts.js looks like:

    const path = require('path')
    const rollup = require('rollup')
    const reimportFrom = require('./scripts/reimport-from')
    const varsPathname = path.resolve('./scripts/vars.js')
    const {
    = require(varsPathname)
    module.exports = {
      // ---------------------------------------------------------------------------
      // Dist
      predist: `mkdir -p ${distPathname}`,
      dist: 'rollup -c',
      postdist: `chmod 755 ${binPathname}`,
      // ---------------------------------------------------------------------------
      // Publish
      prepublish: 'run test',
      publish: 'npm publish',
      // ---------------------------------------------------------------------------
      // Test
      pretest: 'NODE_ENV=test run -q dist',
      test: "mocha -s 400 test/init.js './test/*.test.js' './test/**/*.test.js'",
      watchtest() {
        process.env.NODE_ENV = 'test'
        return Promise.all([
        ]).then(([, config]) => {
          const {spawn, spawnSync} = require('child_process')
          const {watch} = require('chokidar')
          const {distPathname, mainPathname} = require(varsPathname)
          const testPathname = path.resolve('test')
          const wdRegExp = new RegExp(`^${process.cwd()}/`)
          const rollupWatcher =
          const runTest = () => {
            spawnSync('sh', ['-c', this.test], {stdio: 'inherit'})
          const debounceTimeoutMilliseconds = 250
          let debounceTimeoutId
          let ready = false
          const watchRollupOnce = (event) => {
            if (!ready && event.code === 'END') {
              ready = true
              rollupWatcher.removeListener('event', watchRollupOnce)
              const testWatcher = watch(testPathname)
              const watchTestOnce = () => {
                testWatcher.on('all', (type, pathname) => {
                  debounceTimeoutId = setTimeout(runTest, debounceTimeoutMilliseconds)
              testWatcher.once('ready', watchTestOnce)
          rollupWatcher.on('event', watchRollupOnce)
          rollupWatcher.on('event', (event) => {
            switch (event.code) {
              case 'BUNDLE_END':
                const input = event.input
                const output = event.output
                  .map((x) => x.replace(wdRegExp, ''))
                  .join(`\n${new Array(input.length).join(' ')}`)
                console.log(input, '', output)
              case 'ERROR':
              case 'FATAL':
                throw event.error
      // ---------------------------------------------------------------------------

    The first thing you’ll see is that run supports imports. Now you can share modules between the code you build and the code that builds it.

    Then there are comments and whitespace, which make automation tasks easier to write, read, and maintain.

    Finally, the watchtest script is actually a plain-old JavaScript function. Just like other scripts, functions will be passed whatever arguments you define after the -- in run <command> [-- <args>...].

    Imagine that.


    Environment Variables


    Because npm_package_* variables are useful to shell scripts that depend on values stored in package.json, run will define most of them exactly as npm does. However, there are a couple of exceptions.

    Unlike, npm run, run will not attempt to normalize package.json keys or values. For example, run won’t produce the following environment variables unless they are explicitly included in package.json:

    • npm_package_bugs_url
    • npm_package_homepage
    • npm_package_readmeFilename

    Similarly, run does not transform values like npm_package_repository_url.

    For a complete description of how npm run transforms package.json, see: npm/normalize-package-data.


    In the interest of simplicity in both implementation and behavior, run does not read npm’s configuration nor does it define npm_config_* variables.

    Other npm_*

    run defines a few miscellaneous npm environment variables as well:

    • npm_execpath (as run_execpath – npm isn’t running)
    • npm_lifecycle_event
    • npm_lifecycle_script
    • npm_node_execpath


    run was inspired in part by “An alternative to npm scripts” by James Forbes.


    npm i run-simple

    DownloadsWeekly Downloads






    Unpacked Size

    65.1 kB

    Total Files


    Last publish


    • mikol