A developer-friendly process runner for monorepos and multi-service projects. It reads a blade.yaml file (or .yaml files in a .blade directory) and:
- starts one or more services concurrently
- watches files and restarts services on changes
- forwards output to your terminal
- handles graceful shutdown
- provides a quick status glance via a signal
Blade is a small CLI you install with Go. In each repository, you define services in blade.yaml (command to run, what to watch, env vars, output preferences, etc.). Then run blade run to boot them all, or specify a subset by name.
- Language/Stack: Go (Go modules)
- Frameworks/Libraries:
gopkg.in/yaml.v3for config parsing, simple internal file-watcher, colored logging viapkg/colorterm - Package manager: Go modules (
go.mod) - Entry point:
main.go(binary namebladewhen installed)
- Go 1.24+ (module declares
go 1.24.1) - macOS or Linux are expected to work
- TODO: Confirm Windows support and document any limitations
go install github.com/mertenvg/blade@latestMake sure your Go bin dir, typically $HOME/go/bin, is on your $PATH.
Alternatively, build locally from source:
git clone https://github.com/mertenvg/blade.git
cd blade
go build -o blade .# run all services (except those marked skip: true)
blade run
# run only selected services by name
blade run service-one service-two
# print the current version
blade version
# check if a newer version is available
blade check-for-updates
# update to the latest version
blade updateBlade reads configuration from ./blade.yaml or ./.blade/* in the current working directory. If arguments are provided, only the named services are run; otherwise, all non-skipped services are started.
Signals and status:
- Send SIGINT or SIGTERM (e.g., Ctrl+C) to gracefully stop all services.
- Send SIGINFO to print a live status snapshot (active/inactive, pid, uptime).
- Note: On macOS SIGINFO can be triggered with Ctrl+T. On Linux this varies.
- TODO: Document platform-specific key combos and behavior for SIGINFO.
Blade uses YAML to define services. Minimum per-service fields are: name and run.
Example (see full examples in example/blade.yaml and example2/.blade/*):
- name: service-two
from: parent-configuration-name
inheritEnv: true
env:
- name: YOUR_VAR
value: "your value"
- name: VAR_EMPTY_QUOTE
value: ""
watch:
fs:
paths:
- example/cmd/service-one/main.go
run: go run example/cmd/service-one/main.go
output:
stdout: os
stderr: os
# or write to a file (directories are created automatically):
# stdout: "file:./logs/{service-name}.out"
# stderr: "file:./logs/{service-name}.err"Schema (inferred from code):
- Service fields (
internal/service/service.go):name(string) — requiredrun(string) — required; shell command to start the serviceonce(string) — optional; command executed a single time before the service is started for the first timebefore(string) — optional; command executed every time before the service starts, including restarts triggered by the file watcherwatch(object) — optional; file watching configfs.path(string) — single path to watchfs.paths(array) — multiple paths to watchfs.ignore(array) — glob-like patterns to ignore (*,**supported)
inheritEnv(bool) — if true, inherit current process env for the child; if false, start with an empty envenv(array) — environment variable entries:name(string) — variable namevalue(string, optional) — explicit value; if omitted, the current environment value is used (may be empty)
dir(string) — working directory for the command (defaults to.)output(object) — where to pipe stdio:stdout(string) —ospasses stdout to the terminal;file:<path>writes to a file (created/appended); omit to discardstderr(string) —ospasses stderr to the terminal;file:<path>writes to a file (created/appended); omit to discardstdin(string) — when NOT set toos, stdin is passed through to the terminal (current behavior in code)- The
{service-name}placeholder infile:paths is replaced with the service'snamevalue at runtime
sleep(int, milliseconds) — delay before restarting after a service exitsskip(bool) — do not start this service when no explicit list is provideddnr(bool) — do-not-restart flag used on exit/shutdown- Blade auto-sets
BLADE_SERVICE_NAMEfor each child process. - A small PID helper in
pkg/bladewrites.<service>.pidon start and deletes it on exit if your service importsgithub.com/mertenvg/blade/pkg/bladeand callsblade.Done()on shutdown (seeexample/cmd/service-one). - Exponential backoff is applied when a service fails to start; backoff resets after a successful run.
- Reserved/Injected by Blade:
BLADE_SERVICE_NAME— set for child processes to the current service name. Used bypkg/bladeto manage PID files.
- From config (
env):- If
valueis provided, that value is used. - If
valueis omitted, the current environment value is captured and forwarded (may be empty). - If
valuecontains{$VAR_NAME}it will be replaced with the value of theVAR_NAMEenvironment variable.
- If
inheritEnv: truestarts the child with the full current environment; otherwise, the child starts with an empty environment and only variables defined inenvare present.- The repository currently includes a placeholder test in
example/cmd/service-one/main_test.gothat is intentionally ignored by the default ignore patterns. - TODO: Add unit tests for the watcher, service lifecycle, and YAML parsing.
- TODO: Add integration tests that spin up short-lived example services and verify restart behavior and signal handling.
- Build:
go build -o blade . - Install:
go install github.com/mertenvg/blade@latest - Run locally without installing:
go run . run - Allow tags per service in
blade.yamlto filter by tag when running - Document Windows support and SIGINFO behavior across platforms
- Add unit and integration tests
- Add support for multiple yaml files in a single directory for larger configurations
- Allow services to inherit from a parent configuration
- Make values dynamic so environment variables can be used values using
{$VARIABLE_NAME}
Behavioral notes:
Example services are under
example/cmd/*with a sampleexample/blade.yaml.Run the example from the repo root:
cp example/blade.yaml ./blade.yaml ./blade run # if you built locally # or, if installed in PATH: blade run
Run tests (once added):
go test ./.... ├── main.go # CLI entry point ├── version.go # version, check-for-updates, update commands ├── internal/ │ └── service/ │ ├── service.go # service lifecycle (start/restart/exit/status, env, output) │ └── watcher/ │ └── watcher.go # simple FS watcher with ignore patterns ├── pkg/ │ ├── blade/blade.go # PID helper using BLADE_SERVICE_NAME │ └── colorterm/colorterm.go # colored console output ├── example/ │ ├── blade.yaml # sample configuration │ └── cmd/ # toy services for demonstration ├── go.mod / go.sum # Go modules ├── README.md # this file └── LICENSEThe version is automatically embedded by Go when installed via
go install github.com/mertenvg/blade@<version>. No hardcoded version exists in the source code. For custom builds, the version can be injected via ldflags:go build -ldflags "-X main.version=v1.2.3" -o blade .
There are no external script runners (e.g., Makefile) in this repo. Useful Go commands:
This project is licensed under the terms of the license in
LICENSE.