ultra-runner is a free, open source build & deployment project written in TypeScript and released under Apache-2.0. It has 1,249 GitHub stars, 39 forks and 60 open issues, and was last pushed 12 hours ago. On this registry it ranks #57 of 84 tracked projects in Build & Deployment, with 5 head-to-head comparisons available. It gained 2 stars over the last 30 tracked days.

What is ultra-runner?

Ultra Runner is an open-source, zero-configuration script runner and build tool for JavaScript and TypeScript monorepos, built for teams that keep many packages in one repository and need to run scripts and builds across all of them quickly.

What it is

Ultra Runner is a TypeScript command-line tool distributed on npm under the package name ultra-runner, which exposes the ultra command. It is licensed under Apache-2.0 and lives in the build and deployment area of developer tooling. The tool detects the packages in an existing monorepo and gives a single command that runs package scripts, node_modules binaries, or system binaries across the repository, either in one package or recursively through all of them. It is deliberately non-intrusive: no changes are required to the package.json files of the packages it operates on, and it works out of the box against a repository that already exists.

The problem it solves is the slow, awkward orchestration of scripts in a multi-package repository. Workspace managers such as Lerna, Yarn workspaces, npm@7, and pnpm describe where packages live and how they depend on each other, but they do not decide which packages actually need rebuilding or how to fan a command out across them efficiently. Ultra Runner parses the workspace definitions those tools already produce, keeps track of file changes in the repository, and only builds a package when something in it changed. It resolves workspace dependencies so parallel builds run in a usable order, and it hijacks npm, pnpm, yarn, and npx calls so that a script such as ultra lint:fix spawns exactly one child process with the correct command instead of an intermediate yarn process. That is the specific behaviour it replaces: the hand-written chains of workspace-manager invocations that most monorepos accumulate.

Key capabilities

  • Workspace detection without configuration. Packages are found in existing lerna, yarn, npm@7, and pnpm workspaces, or through a recursive search when no monorepo manager is present.
  • Incremental builds. ultra tracks file changes in the repository and only actually builds a package when that package needs it.
  • Parallel builds with dependency ordering. Packages are built concurrently by default, and workspace dependencies are resolved automatically and used to schedule the parallel builds.
  • One command for anything executable. Package scripts, node_modules binaries, and system binaries all run through the same interface, recursively across the repository.
  • Faster script execution through hijacking. npm, pnpm, yarn, and npx calls inside scripts are intercepted, and shell operators such as &&, ;, and || are interpreted; configured pre and post scripts still run as they would under npm run or yarn run.
  • Recursive execution with filtering and concurrency. -r or --recursive runs a command in every package except the root, --root adds the root back, --concurrency changes the default limit of 10, and --filter narrows the set by package name or subdirectory using wildcards, with a + prefix pulling in dependencies.
  • Output zooming and process monitoring. When many commands run in parallel, only the last lines of each are shown so concurrent output stays on screen, and a full log is written once they finish; a node process monitor, similar to top for node, is also included.

Who uses it and how

  • Teams already on Lerna, Yarn workspaces, npm@7, or pnpm adopt it without migrating their workspace layout, because it reads those definitions rather than replacing them.
  • Continuous integration jobs use recursive execution with the default concurrency of 10 to build or test every affected package, scoping the work with --filter when only part of the repository matters.
  • Developers running many --watch builds at once rely on output zooming to keep the terminal readable, with the full log available after each command completes.
  • Projects that cannot or will not edit their package.json files use the non-intrusive mode to speed up script execution without touching package configuration.
  • Debugging sessions use the built-in node process monitor to inspect what the tool has spawned.

Getting started

Ultra Runner is published on npm as the ultra-runner package and installs from there, after which the ultra command is available. Once installed, scripts are run with commands such as ultra lint in a single package or ultra -r build across the whole workspace.

How it compares

The tools named in the project's own material are Lerna, Yarn workspaces, npm@7, and pnpm, all of which Ultra Runner treats as sources of workspace information rather than as competitors to be removed. It sits on top of those managers: they define the package graph and handle installation, while Ultra Runner decides what to run, what actually needs rebuilding, and how to schedule that work concurrently.

When to use it โ€” and when not to

This is a tool for repositories that genuinely contain multiple packages; a single-package project gains nothing from workspace detection, incremental build tracking, or recursive execution. Adopters should also weigh the project's modest footprint, with 39 forks and 60 open issues at the time of writing, and note that the published documentation excerpt covers command behaviour in depth but does not spell out deployment, configuration files, or support arrangements beyond what the package.json options allow. Teams that need guarantees about long-term maintenance or formal support should look elsewhere, while teams comfortable with a small, recently active Apache-2.0 project will find it does exactly what it claims.

project readme (upstream, from github) โ€” read inline

:runner: :mountain: Ultra Runner

github badge Coverage Status npm GitHub GitHub top language Renovate

Ultra fast monorepo script runner and build tool.

:sparkles: Features

  • zero-config: works out of the box with your existing monorepo
  • non-intrusive: no need to make any changes to your packages.json files
  • workspaces: detects packages in existing lerna, yarn, npm@7 and pnpm workspaces, or recursively searches them
  • ultra fast builds: ultra keeps track of file changes in your repo and only actually build a package when needed
  • parallel builds: ultra builds your packages concurrently by default
  • workspace dependencies: workspace dependencies are automatically resolved and used for parallel builds
  • execute anything: one command to run package scripts, node_modules binaries or system binaries, recursively in your repository.
  • faster script execution: ultra hijacks any npm, pnpm, yarn and npx calls for faster execution.
  • concurrency within scripts: you can add optional configuration to package.json to run parts of a script in parallel. No need to change the actual scripts
  • filtering: filter on package names or subdirectories
  • monitoring: node process monitor (like top for node)
  • output zooming when executing multiple commands in parallel, ultra will try to keep as much concurrent output on the screen as possible, by only showing the last lines of the commands. Once the commands complete running, a full log is written to the terminal. This is very useful when building a bunch of packages with --watch for instance.
  • missing scripts when executing scripts recursively, only packages that have the script defined, will execute it.

๐ŸŽฅ View Demo

Workspaces

:nerd_face: Smart

Ultra parses your package.json and hijacks any npm run, yarn and npx calls. Shell operators like &&, ; and || are also interpreted.

For example:

{
  "scripts": {
    "lint": "yarn lint:ts && yarn lint:eslint && yarn lint:docs",
    "lint:eslint": "npx eslint bin/*.js src/*.ts __tests__/*.ts --cache",
    "lint:docs": "npx markdownlint README.md",
    "lint:ts": "npx tsc -p tsconfig.build.json --noEmit",
    "lint:fix": "yarn lint:eslint --fix"
  }
}

Running ultra lint Ultra Lint

Running ultra lint:fix will spawn exactly one child process, directly with the correct command, instead of spawning yarn intermediately

Ultra will additionally execute any configured pre and post scripts, just like npm run and yarn run.

:palm_tree: Recursive Execution

When using -r or --recursive, the command will be executed in every package of your repository, excluding the root package. If you also want to run in the root package, combine --recursive with --root. Commands are always run concurrently with a default concurrency of 10 (can be changed with --concurrency)

Ultra finds packages based on your monorepo workspace:

  • lerna
  • pnpm
  • yarn workspace
  • when no monorepo manager was found, we look recursively for packages

Use --filter to filter packages in the workspace. The filter argument can use wildcards to filter package names and/or subdirectories:

$ ultra -r --filter "@scope/app" pwd
...

$ ultra -r --filter "@scope/*" pwd
...

$ ultra -r --filter "apps/*" pwd
...

When the filter is prefixed with a +, then all dependencies of the filtered packages will also be included. For example, let's say you have a package "app1" that depends on "lib1", then using the filter +app1, will execute the command on both app1 and lib1, using the workspace topology.

:package: Builds

Ultra automatically detects workspace dependencies, while still allowing parallel builds. Packages are build concurrently as soon as their dependencies are build (also concurrently). Every package directory contains a .ultra.cache.json file that contains hashes of all files and build artifacts in your repository. Internally this uses git ls-files for files under source control and simple mtime timestamps for build artifacts. When building a package, the current state is compared with the .ultra.cache.json. Builds are skipped when no changes were detected.

Optimized builds using the dependency tree and files cache, are automatically triggered when running the build script or using --build with a custom script or command.

All commands below will trigger optimized builds.

$ ultra -r --build
...

$ ultra -r build
...

$ ultra -r --build mycustombuildscript
...

If for some reason you want to rebuild a package, use --rebuild or rebuild.

If you want some files to be excluded from the .ultra.cache.json, you can create a .ultraignore file. The format is similar to .gitignore. Whenever a file changes that is listed in your .ultraignore, a rebuild will not be triggered.

:bar_chart: Monitor

With ultra --monitor you can easily monitor all running node processes on your machine.

For every process, you can also see the package where the command was executed and a clean command line.

Monitor

:zap: Fast

Ultra parses your package.json scripts and will only execute the commands that are really needed. Any script interdependencies are resolved during the parsing stage. This ensures there's pretty much no overhead in execution by Ultra itself, since it's only running once. yarn run or npm run on the other hand, will spawn new yarn or npm child processes as needed by the package scripts.

npm run npx yarn yarn exec ultra
package.json scripts :white_check_mark: :x: :white_check_mark: :x: :white_check_mark:
./node_modules/.bin/ :x: :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark:
system binaries :x: :white_check_mark: :x: :white_check_mark: :white_check_mark:
execution overhead (1) 250ms 60ms 220ms 200ms 65ms

1. each program was run 10x with the command true or {scripts:{"true":"true}} to calculate the execution overhead

Suppose you would want to run a script that calls 5 other scripts by using && and/or post/pre.

  • Using yarn, you would have a total overhead of 2.5s (10x 250ms)
  • Using ultra, you hit the overhead only once, so the total overhead would still be 65ms

To make execution ultra fast, you can configure which scripts should be ran concurrently.

:grey_exclamation: there's no need to switch your scripts over to ultra. Even with the optional configuration you can still use yarn or npm to run your scripts if you want to.

Example builds:

yarn ultra not concurrent ultra concurrent
build Ultra-Runner 8.9s 7.2s 5.1s
build Devmoji 16s 13s 8s

:art: Formatting

There are three output formats that each can be combined with --silent to hide command output.

--pretty is the default. It shows output in a hierarchical way and uses spinners to see exactly what's happening. Make sure to check out the animation at the top of this page. Every executed step shows the execution time.

--pretty combined with --silent is useful if you're only interested to see the overview:

--no-pretty doesn't use spinners and prefixes command output with the command name. This is useful for logging purposes.

Combining --no-pretty with --silent shows a flat overview.

--raw will show the exact output as you would expect when running the commands stand alone. If the command you're executing is interactive (reads from stdin), then this is the mode you should use.

:dizzy: Getting Started

Install with npm or yarn

globally

npm install -g ultra-runner
yarn global add ultra-runner

locally inside your project. use with npx ultra

npm install --dev ultra-runner
yarn add --dev ultra-runner

Now run ultra --info within your repository to see everything related to your monorepo

See optional configuration for information on how to setup concurrent script execution.

:rocket: Usage

$ ultra --help
Usage: ultra [options] <cmd> [cmd-options]

Workspace:
  --recursive, -r  Run command in every workspace folder concurrently                                      [boolean]
  --filter         Filter package name or directory using wildcard pattern                                  [string]
  --root           When using --recursive, also include the root package of the workspace                  [boolean]
  --concurrency    Set the maximum number of concurrency                                      [number] [default: 10]

Status:
  --info  Show workspace dependencies                                                                      [boolean]
  --list  List package scripts. Also works with --recursive                                                [boolean]
  --monitor           Show node process list, updated every 2 seconds                                      [boolean]
  --monitor-interval  Set process list interval in seconds                                     [number] [default: 2]

Build:
  --build, -b  Use dependency tree to build packages in correct order                                      [boolean]
  --rebuild    Triggers a build without checking for file changes                                          [boolean]

Formatting:
  --pretty  enable pretty output, spinners and separate command output. Default when a TTY [boolean] [default: true]
  --raw     Output only raw command output                                                                 [boolean]
  --silent  Skip script output. ultra console logs will still be shown                                     [boolean]
  --color   colorize output                                                                [boolean] [default: true]

Options:
  --version      Show version number                                                                       [boolean]
  --dry-run, -d  Show what commands would be executed, without actually executing them                     [boolean]

:gear: Optional Configuration

To allow parallel execution of your scripts, you can specify scripts that should run concurrently, in your package.json.

{
  "scripts": {
    "lint:eslint": "npx eslint bin/*.js src/*.ts __tests__/*.ts --cache",
    "lint:docs": "npx markdownlint *.md",
    "lint:ts": "npx tsc -p tsconfig.build.json --noEmit",
    "lint": "yarn lint:eslint && yarn lint:docs && yarn lint:ts",
    "prebuild": "yarn lint && yarn jest",
    "build": "..."
  },
  "ultra": {
    "concurrent": ["lint"]
  }
}
  • yarn build will run the lint and jest commands sequentially
  • ultra build will run all lint commands concurrently and then execute jest. (note that we can also add prebuild to concurrent, since tests don't depend on linting. this way all commands would run concurrently)

Frequently asked questions

Is ultra-runner free to use?

ultra-runner is open source under the Apache-2.0 licence. There is no licence fee and no seat count โ€” you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does ultra-runner do?

๐Ÿƒโ›ฐ Ultra fast monorepo script runner and build tool

What is ultra-runner written in?

ultra-runner is primarily written in TypeScript. Its source is publicly available at https://github.com/folke/ultra-runner, and it has 1,249 GitHub stars.