run dev scripts
Project description
ds: run dev scripts
Why?
I often need to run scripts to build my code, run my server, lint my files, etc. Every project seems to use a different tool (e.g., make
, ant
, npm
, pnpm
, pdm
), yet the basic commands I use in each project are largely the same. So I was inspired to build a very simple task runner.
- Minimal magic: Designed to use a familiar syntax and a few clear rules where possible. Not trying to recreate
make
. - Low surprise: checks for cycles and raises helpful error messages when things are wrong.
- Works with existing files: including
package.json
,pyproject.toml
, andCargo.toml
files. - Run multiple tasks:
[npm|yarn|pnpm|bun|pdm] run
can only run one task at a time. - Minimal dependencies: working on removing all of these (see #31)
- python (3.8+)
tomli
(for python < 3.11)graphlib_backport
(for python < 3.9)
Install
python -m pip install ds-run
# or, if you use uv:
uv pip install ds-run
Example
Create a ds.toml
file in the top-level of your project or you can also put this configuration in package.json
, pyproject.toml
, or Cargo.toml
to reduce cruft.
# Example: Basic `ds` configuration.
[scripts]
clean = "rm -rf build/"
build = "mkdir $@" # pass arguments
all = ["clean", "build -p build"] # a composite task
Now you can list the tasks with ds --list
or just ds
:
# Found 3 tasks in ds.toml
clean:
rm -rf build/
build:
mkdir $@
all:
['-clean', 'build -p build']
Run the tasks.
ds clean
# => rm -rf build/
ds build: some-folder
# => mkdir some-folder
ds all
# => rm -rf build/
# => mkdir -p build
Read more:
Usage
Usage: ds [--help | --version] [--debug]
[--cwd PATH] [--file PATH]
[--list | (<task>[: <options>... --])...]
Options:
-h, --help
Show this message and exit.
--version
Show program version and exit.
--debug
Show debug messages.
--cwd PATH
Set the working directory (default: task file parent).
-f PATH, --file PATH
File with task definitions (default: search in parents).
-l, --list
List available tasks and exit.
<task>[: <options>... --]
One or more tasks to run with task-specific arguments.
Use a colon (`:`) to indicate start of arguments and
double-dash (`--`) to indicate the end.
If the first <option> starts with a hyphen (`-`), you may omit the
colon (`:`). If there are no more tasks after the last option, you
may omit the double-dash (`--`).
Configuration File
If you don't provide a config file using the --file
flag, ds
will search the current directory and all of its parents for files with these names in the following order:
ds.toml
.ds.toml
package.json
pyproject.toml
Cargo.toml
The first file found will be used.
ds
currently supports json
and toml
files. It searches for the following keys in the following order:
scripts
fords.toml
,.ds.toml
,package.json
tool.ds.scripts
forpyproject.toml
tool.pdm.scripts
forpyproject.toml
package.metadata.scripts
forCargo.toml
The first key that is found is used and should be a mapping of task names to commands.
Where should I put my config?
To avoid making lots of top-level files, ds
tries to use common project configuration files.
- Node:
package.json
underscripts
(see Lifecycle Events Not Supported) - Python:
pyproject.toml
under[tool.ds.scripts]
- Rust:
Cargo.toml
under[package.metadata.scripts]
- Other:
ds.toml
under[scripts]
Task Names
- Task names are strings, that are usually short and all lowercase.
- They can have a colon (
:
) in them, likepy:build
, or other punctuation, likepy.build
. - If the name starts with a hash (
#
) it is ignored. This comes frompackage.json
in which its comment to add "comments" as JSON keys. - Don't start a task name with a hyphen (
-
); it usually indicates Error Suppression. - Don't end a task name with a colon (
:
); we use this to indicate Command-line Arguments
Commands
ds
ultimately converts all commands into strings to be executed with subprocess.run
.
Basic Command
# Example: Basic commands become strings.
[scripts]
ls = "ls -lah"
no_error = "-exit 1" # See "Error Suppression"
# We also support `pdm`-style commands.
# The following all produce the same command as `ls` above.
ls2 = { cmd = "ls -lah" }
ls3 = { cmd = ["ls", "-lah"] }
ls4 = { shell = "ls -lah" }
A basic command is just a string of what should be executed in a shell.
- Supports most
pdm
-style commands (exceptcall
, see #32) - Supports argument interpolation
- Supports error suppression
Composite Command
# Example: Composite commands call other tasks or shell commands.
[scripts]
build = "touch build/$1"
clean = "rm -rf build"
all = ["clean", "-mkdir build", "build foo", "build bar", "echo 'Done'"]
# We also support pdm-style composite commands.
# The following is equivalent to `all`.
all2 = { composite = [
"clean",
"-mkdir build",
"build foo",
"build bar",
"echo 'Done'",
] }
A composite command consists of a series of steps where each step is the name of another task or a shell command.
- Supports argument interpolation
- Supports error suppression
Argument Interpolation
Commands can include parameters like $1
and $2
to indicate that the command accepts arguments.
You can also use $@
for the "remaining" arguments (i.e. those you haven't yet interpolated yet).
Arguments from a composite command precede those from the command-line.
Command-line Arguments
When calling ds
you can specify additional arguments to pass to commands.
ds build: foo -- build: bar
This would run the build
task first with the argument foo
and next with the argument bar
.
A few things to note:
- the colon (
:
) after the task name indicates the start of arguments - the double dash (
--
) indicates the end of arguments
If the first argument to the task starts with a hyphen, the colon can be omitted. If there are no more arguments, you can omit the double dash.
If you're not passing arguments, you can put tasks names next to each other:
ds clean test
Error Suppression
If a command starts with a hyphen (-
), the leading hyphen is removed before the command is executed and the command will always produce an error code of 0
(i.e. it will always be considered to have completed successfully).
This is particularly useful in composite commands where you want subsequent steps to continue even if a particular step fails. For example:
# Example: Error suppression lets a command continue even if it fails.
[scripts]
cspell = "cspell --gitignore '**/*.{py,txt,md,markdown}'"
format = "ruff format ."
die = "-exit 1" # returns error code of 0
lint = ["-cspell", "format"] # run format even if cspell finds some misspelled words
Error suppression is currently only available in configuration files and not on the command-line API (see #22).
Lifecycle Events Not Supported
Some task runners support running additional pre- and post- tasks. However, this is currently not supported (see #24). Part of the reason is that it obscures the relationship between tasks and creates surprise if you have to have two tasks with unfortunate names.
As more explicit alternative is to use composite commands to clearly describe the relationship between a task and its pre- and post- tasks.
Inspirations
I've used several task runners, usually as part of build tools. Below is a list of tools used or read about when building ds
.
-
1976:
make
(C) - Together with its descendants,make
is one of the most popular build & task running tools. It is fairly easy to make syntax errors and the tab-based indent drives me up the wall. -
2000:
ant
(Java) - an XML-based replacement formake
. I actually liked usingant
quite a bit until I stopped writing Java and didn't want to havejava
as a dependency for mypython
projects. -
2008:
gradle
(Groovy/Kotlin) - Written for thejvm
, I pretty much only use this for Android development. Can't say I love it. -
2010:
npm
(JavaScript) - Being able to add a simplescripts
field topackage.json
made it very easy to run dev scripts. Supportspre
andpost
lifecycle tasks. -
2010:
pdm
(Python) - Supports 4 different types of tasks includingcmd
,shell
,call
, andcomposite
. -
2016:
yarn
(JavaScript) - An alternative tonpm
which also supports command-line arguments. -
2016:
pnpm
(JavaScript) - Another alternative tonpm
which supports many more options including running tasks in parallel. -
2016:
just
(Rust) - Defines tasks in ajustfile
, similar tomake
. Supports detecting cycles, running parallel, and many other options. -
2016:
cargo-run-script
(Rust) - UsesCargo.toml
to configure scripts and supports argument substitution ($1
,$2
, etc.). -
2017:
cargo-make
(Rust) - Very extensive port ofmake
to Rust defining tasks inMakefile.toml
. -
2022:
hatch
(Python) - Defines environment-specific scripts with the ability to suppress errors, likemake
. -
2023:
bun
(Zig) - An alternative tonode
andnpm
.
License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.