daizong

`package.json` script runner for ES Modules

Usage no npm install needed!

<script type="module">
  import daizong from 'https://cdn.skypack.dev/daizong';
</script>

README

daizong 🏃‍♂️

Build Status npm version Node.js Version

package.json script runner for ES Modules. daizong supports the following features out of the box:

  • Run tasks sequentially or in parallel
  • Environment variables
    • Set environment variables for a specific task
    • Set default environment variables for all tasks
    • Define groups of environment variables to be inherited by tasks
  • Define tasks in groups
  • Private tasks
  • Allow continue-on-error
  • Actions (create directories, delete files / directories)

Breaking changes

0.20.0+

To avoid ambiguity between task names and arguments passed to tasks. Starting from 0.20.0, a task path has to be separated by - instead of a space:

# Prior to 0.20.0
daizong build windows --args # Don't use, deprecated.

# 0.20.0+
daizong build-windows --args

Usage

  • Install daizong as a dev dependency npm i -D daizong (you can skip this if you want daizong to be installed globally).
  • Create a daizong.config.js to define tasks (see examples below).
  • Use npx daizong <task> --arg1 --arg2 to run a specific task.

It's also strongly recommended to use daizong-cli to start tasks in a shorter way.

Installation:

npm i -g daizong-cli

Now instead of npx daizong <task>, you can do:

dz task

Note that daizong-cli works regardless of whether daizong is installed locally or not.

Examples (daizong vs npm scripts)

package.json:

{
  "scripts": {
    "dev": "tsc -b src -w"
  }
}

daizong:

export default {
  dev: 'tsc -b src -w',
};

Run tasks sequentially

package.json:

{
  "scripts": {
    "dev": "touch a.md && touch b.md"
  }
}

daizong:

export default {
  dev: ['touch a.md', 'touch b.md'],
};

Run tasks in parallel

We'll need 3rd-party libraries like(concurrently) to achieve this in package.json:

{
  "scripts": {
    "dev": "concurrently \"touch a.md\" \"touch b.md\""
  }
}

daizong supports it out of the box:

Shorthand and full task definitions

Most tasks you see above are defined as a command string or an array of command strings:

export default {
  dev: ['touch a.md', 'touch b.md'],
};

This is a shorthand task definition. As we're moving on to more advanced task features, the shorthand task definition is no longer suited and we can switch to its full definition:

export default {
  // Shorthand task definition is either a string or an array of strings.
  task1: 'echo hi',

  // The `task1` above can be written in its full form.
  task1: {
    run: 'echo hi',
    /* More task settings can be used here. */
  },
};
export default {
  dev: {
    run: ['touch a.md', 'touch b.md'],
    parallel: true,
  },
};

Reuse a task

package.json:

{
  "scripts": {
    "dev": "concurrently \"npm run touch1\" \"npm run touch2\"",
    "touch1": "touch a.md",
    "touch2": "touch b.md"
  }
}

daizong:

export default {
  dev: {
    // Use `#<task_name>` to call an existing task.
    run: ['#touch1', '#touch2'],
    parallel: true,
  },
  touch1: 'touch a.md',
  touch2: 'touch b.md',
};

Grouped tasks

Tasks can be grouped to improve readability.

export default {
  build: {
    win: {
      run: 'echo Windows build started',
    },
    linux: {
      run: 'echo Linux build started',
    },
    all: {
      run: '#build-win', '#build-linux',
      parallel: true,
    }
  },
};

To run a specified task in a group, separate all parent groups with -:

dz build-linux
dz build-all

Environment variables

To support all major operating systems, you need to use 3rd-party libraries like(cross-env) to achieve this in package.json scripts.

package.json:

{
  "scripts": {
    "build": "cross-env NODE_ENV=production tsc -b src",
    "dev": "cross-env NODE_ENV=development tsc -b src -w"
  }
}

daizong supports it out of the box:

export default {
  build: {
    run: 'tsc -b src',
    // Use `env` to specify environment variables.
    env: {
      NODE_ENV: 'production',
    },
  },
  dev: {
    run: 'tsc -b src -w',
    env: {
      NODE_ENV: 'development',
    },
  },
};

Default environment variables are also supported. Once configured, they will be automatically applied to all tasks:

export default {
  // "_" is a preserved field for configuration.
  _: {
    defaultEnv: {
      NODE_ENV: 'development',
    },
  },
  dev: {
    // NODE_ENV is 'development'
    run: 'tsc -b src -w',
  },
  build: {
    // NODE_ENV is 'production'
    run: 'tsc -b src',
    env: {
      NODE_ENV: 'production',
    },
  },
};

You can also define groups of environment variables to be inherited by tasks:

export default {
  // "_" is a preserved field for configuration.
  _: {
    defaultEnv: {
      NODE_ENV: 'development',
    },
    // Use `envGroups` to define multiple groups of environment variables.
    envGroups: {
      production: {
        NODE_ENV: 'production',
        compression_level: 'max',
      },
    },
  },
  dev: {
    // NODE_ENV is 'development'
    run: 'tsc -b src -w',
  },
  build-windows: {
    run: 'build',
    env: {
      platform: 'windows'
    },
    // This task has all environment variables defined in "production" group.
    envGroups: ['production'],
  },
  build-macos: {
    run: 'build',
    env: {
      platform: 'macos'
    },
    // This task has all environment variables defined in "production" group.
    envGroups: ['production'],
  },
};

Environment variable definitions precedence

Smaller numbers indicate higher precedence.

  1. Task.env
  2. Task.envGroups (last group overwrites preceding groups like Object.assign)
  3. _.defaultEnv

Continue on error

  • ignoreError available on all tasks, defaults to false. If true, task failure is ignored and won't stop execution.
  • continueOnChildError only available on tasks with multiple subtasks. It controls if pending subtasks continue to run when one subtask fails. Defaults to false.

Example:

export default {
  build: {
    run: [
      '#clean',
      // The `tsc` command will always run regardless of the result of clean command.
      'tsc',
    ],
  },
  clean: {
    run: 'echo cleaning...',
    ignoreError: true,
  },
};

Private tasks

Tasks that are not intended to be called from outside, and can only be called by other tasks.

// You cannot call the "clean" task via `daizong clean`.
// It can only be called by other tasks.
export default {
  // "_" is a preserved field for configuration.
  _: {
    privateTasks: {
      clean: {
        run: 'echo cleaning...',
      },
    },
  },
  build: {
    run: ['#clean', 'tsc'],
  },
};

Actions

Actions are a set of commonly used commands you can choose to run before or after a task:

export default {
  task: {
    run: 'echo hi',
    before: {
      // Actions ...
    },
    after: {
      // Actions ...
    },
  },
};

daizong currently supports the following actions:

  • mkdir: string creates a directory and its parents if needed.
  • del: string | string[] deletes files or directories based on the given paths or globs. See del for details.
    • Examples: del: 'dist/*.js.map', del: ['a.txt', 'b.txt'].
  • mkdirDel: string = del <dir> + mkdir <dir>.

For example, to create a /dist directory before running task dev, and delete all js.map files when it's done:

export default {
  dev: {
    run: 'echo dev',
    before: {
      mkdir: 'dist',
    },
    after: {
      del: 'dist/*.js.map',
    },
  },
};

Actions can be reused by simply wrapping them in run:

export default {
  prepare: {
    run: {
      mkdir: 'dist',
      del: 'cache',
    },
  },
  dev: {
    run: ['#prepare', 'echo dev'],
  },
  build: {
    run: ['#prepare', 'echo build'],
  },
};

Actions also support parallel execution:

export default {
  prepare: {
    run: {
      mkdir: 'dist',
      del: 'cache',
      parallel: true,
    },
  },
  dev: {
    run: ['#prepare', 'echo dev'],
  },
  build: {
    run: ['#prepare', 'echo build'],
  },
};

Note that when parallel is false (which is the default value), actions are executed sequentially in insertion order:

export default {
  prepare: {
    run: {
      // `del dist` always runs first!
      del: 'dist',
      mkdir: 'dist',
    },
  },
};

The above example is also equivalent to:

export default {
  prepare: {
    run: {
      // `mkdirDel` deletes and then creates the specified directory.
      mkdirDel: 'dist',
    },
  },
};

Aliases

Note: To keep things simple, aliases are only allowed in top-level public tasks.

You can set an alias for a public task:

export default {
  build: {
    run: ['#build-windows', '#build-macos', '#build-linux'],
    parallel: true,
    alias: 'b',
  },
};

Now you can start the task by using either build or its alias b.

Pass arguments to a task

Just append the arguments to task path:

export default {
  hello: {
    run: 'echo hello',
  },
};
dz hello i am zzz --arg1 --arg2

Which runs:

echo hello i am zzz --arg1 --arg2

NOTE: Arguments specified before task name are considered daizong arguments, not task arguments. Example:

dz --config <val> build-clean --config <val>

The first --config argument applies to the daizong CLI, while the second --config argument gets passed to task command.

CLI Usage

  Usage
    $ daizong [options] <task-path> [task arguments]

  Options
    --config       Explicitly specify the config file, `--config config.js`
    --verbose      Print verbose information during execution
    --private      Allow private tasks to be called from CLI
    --version, -v  Print version information

  Examples
    $ daizong --verbose test-browser --script-arg1 --script-arg2

Use daizong in a CommonJS module

Use daizong.config.mjs instead of daizong.config.js.

Do this in package.json:

{
  "scripts": {
    "r": "daizong --config daizong.config.mjs"
  }
}