Skip to content

cross-platform application execution

Notifications You must be signed in to change notification settings

kevgo/run-that-app

Repository files navigation


Run that app logo



Run-that-app is a minimalistic cross-platform application runner. It executes native CLI tools on Linux, macOS, Windows, and BSD without requiring a prior installation. The primary use case is running developer tools (linters, analyzers, formatters, etc) in scripts and CI pipelines.

integrating installation and execution

Installing small developer tools at pinned versions across multiple operating systems is a surprisingly hard problem without a good solution.

Run-that-app sidesteps the problem entirely: instead of installing tools, it focuses on running them. For most development workflows, that's what you actually care about.

radically minimalistic

Run-that-app is intentionally minimalistic and non-invasive. It ships as a single stand-alone binary. Following the principle "perfection is achieved not when there is nothing left to add, but when there is nothing left to take away", run-that-app works without:

  • application shims
  • shell integrations
  • hard links
  • environment variables
  • configuration files and settings
  • dependencies
  • plugins
  • custom packaging and container formats
  • a dedicated install step
  • application repositories
  • Docker
  • WASM
  • system daemons
  • sudo
  • emulation
  • IDE plugins
  • any other kind of bloat

Applications are downloaded directly from their original hosting location, typically in 1-2 seconds. Only the executable is stored on disk. Execution is 100% native, with no runtime overhead.

linux windows

installable applications

installation

Linux and macOS:

curl https://raw.githubusercontent.com/kevgo/run-that-app/main/download.sh | sh

Windows (Powershell):

Invoke-Expression (Invoke-WebRequest -Uri "https://raw.githubusercontent.com/kevgo/run-that-app/main/download.ps1" -UseBasicParsing).Content

The installer places the run-that-app executable into the current directory. To install elsewhere, execute the installer from that directory.

Compile from source:

cargo install --git https://github.com/kevgo/run-that-app

usage

rta [run-that-app arguments] <app name>[@<app version>] [app arguments]

Run actionlint at version 1.6.26:

The app version should contain only the version number (e.g. 1.6.26), even if the Git tag is prefixed (e.g. v1.6.26).

Run-that-app arguments must appear before the name of the application to run. The application name is the first argument that does not start with a dash. All following arguments are passed through to the application.

Run ShellCheck version 0.9.0 with arguments --color=always myscript.sh:

rta [email protected] --color=always myscript.sh

list all runnable applications

rta --apps

graceful degredation

Not all applications support all platforms. If binaries aren't distributed for your platform, run-that-app can compile applications from source. If that doesn't work, the --optional flag skips unsupported applications without failing the command.

Example: run ShellCheck only if it is available on the current platform:

rta --optional [email protected] myscript.sh

The --available command reports availability via its exit code.

get the path to the installed executable

The --which command prints the path to the resolved executable.

Example: run go vet with alphavet as a custom vet tool, but only if alphavet is available:

rta --available alphavet && go vet "-vettool=$(rta --which alphavet)" ./...

monitor output

Some tools (e.g. deadcode) report findings via stdout but exit with status code 0. The --error-on-output treats any output as failure.

rta --error-on-output deadcode

list available versions

Show the 10 most recent versions of an application:

rta --versions actionlint

Limit the output to a specific number:

rta --versions=3 actionlint

force installation from source

If precompiled binaries are available (e.g. via GitHub releases), run-that-app use them. If not, it can compile applications from source.

To enforce compilation from source even when binaries exist:

rta --from-source <app>

configuration

Run-that-app supports a configuration file named run-that-app, using the asdf version file format:

actionlint 1.6.26
shellcheck 0.9.0

With this file in place, you no longer need to be specify the version explicitly:

rta actionlint

The file name intentionally differs from asdf and mise to avoid interference.

add an application

Add an application at its latest version (creates the config file if needed):

rta --add actionlint

update all applications

Update all configured applications to their latest versions:

rta --update

globally installed applications

Run-that-app can reuse tools already installed on your system. The executable must be present in the PATH, and the version must be declared as system.

go system 1.21.3

This prefers the system-installed Go. If none is found, Go 1.21.3 is installed and used.

You can restrict acceptable versions for the externally installed app:

go [email protected].* 1.21.3

external version declarations

Some tools define their version in project files (e.g. Go via go.mod). Setting the version to auto enables automatic detection:

go auto

configure color output

Run-that-app emits ANSI colors if STDOUT and STDERR are connected to a TTY. You can override this behavior using the following environment variables:

  • CLICOLOR_FORCE=1 always enables color output, even when not writing to a TTY
  • NO_COLOR=1 disables color output entirely

bundled applications

Some tools are distributed as part of another toolchain. In these cases, specify the version of the bundling application.

npm and npx

npm and npx are provided by Node.js. To use them, specify a Node version:

npm 20.10.0

To run an npm that was installed externally, provide its own version:

You can combine both declarations:

npm [email protected] 20.10.0

This prefers an existing npm ≥ 10.2, otherwise installs Node 20.10.0 and uses the npm version that comes with it.

gofmt

Gofmt is bundled with Go. Specify the Go version:

gofmt 1.21.6

This installs Go 1.21.6 and uses its bundled gofmt.

Usage in a Makefile

Example Makefile integration:

RTA_VERSION = 0.28.0  # version of run-that-app to use

# an example Make target that uses run-that-app
test: tools/rta@${RTA_VERSION}
	tools/rta actionlint

# this Make target installs run-that-app if it isn't installed or has the wrong version
tools/rta@${RTA_VERSION}:
	@rm -f tools/rta*
	@mkdir -p tools
	@(cd tools && curl https://raw.githubusercontent.com/kevgo/run-that-app/main/download.sh | sh)
	@mv tools/rta tools/rta@${RUN_THAT_APP_VERSION}
	@ln -s rta@${RUN_THAT_APP_VERSION} tools/rta

Add tools/rta* to .gitignore.

Q&A

Run-that-app does not support an application I need

Adding a new application is straightforward. See the developer documentation.

Why not use the package manager?

If it works for you, do it. In practice, package managers introduce issues:

  • Different OSes use different package managers. You would need to support Homebrew, Nix, Scoop, Chocolatey, winget, DNF, pacman, apt, pkg, snap, zypper, xbps, portage, etc.
  • Some environments like Windows or bare-bones Docker images don't have a package manager.
  • Not all tools are packaged everywhere.
  • Packaged application versions are often out of your control.
  • Different projects often require different tool versions that would need to be installed in parallel.

Why not use Docker?

Docker solves a different problem: shipping full runtime environments. For development tooling, it often adds unnecessary complexity and bloat:

  • extra OS layers (especially on Windows and macOS)
  • significant storage and memory overhead
  • Docker-in-Docker issues on CI
  • no help with CPU architecture mismatches
  • no solution for binaries hosted on GitHub releases

Why not quickly write a small Bash script that downloads the executable?

Cross-platform Bash scripts quickly become fragile:

  • they depend on external tools (curl, tar, zip)
  • they and the external tools they use can behave differently across systems
  • they don't work natively on Windows

Run-that-app is effectively a cross-platform Bash script, written in a strongly typed programming language with predictable behavior.

An app is not available for my platform

Run-that-app can compile from source. If that fails, it can gracefully degrade.

What about NodeJS, Python, or Ruby tools?

Run-that-app can run the package managers and runtimes for these ecosystems, which then run the respective tool.

An app has complex dependencies

Open an issue. Many cases are solvable.

Why no marketplace?

That marketplace is in the source code. This avoids:

  • weak schema-based configuration languages
  • version skew between runner and marketplace
  • synchronization overhead at runtime

It also enables fully deterministic tooling in locked-down environments.

Related solutions

These other cross-platform package managers might be a better fit for your use case.

asdf

Asdf is a mature cross-platform tool runner based on Bash (now also Go). It relies on plugins and shims and does not support Windows well.

Run-that-app is faster, simpler, and Windows-native.

mise

Mise is a Rust-based successor to asdf with broader scope (env vars, tasks, shell integration).

Run-that-app is much simpler and focuses on doing one thing well.

pkgx

Pkgx provides a polished UX, shell integration, and an app store.

Run-that-app trades polish for simplicity, determinism, and flexibility, including source builds and conditional execution.

About

cross-platform application execution

Resources

Stars

Watchers

Forks

Languages