A developer-friendly process runner for monorepos and multi-service projects. It reads a blade.yaml file 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 $GOPATH/bin (or 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-twoBlade reads configuration from ./blade.yaml 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: name and run.
Example (see full example in example/blade.yaml):
- name: service-one
watch:
fs:
path: cmd/service-one/
ignore:
- node_modules
- .*
- "**/*_test.go"
env:
- name: VAR_1
value: VAL_1
before: echo "do something at first run"
run: go run cmd/service-one/main.go
output:
stdout: os
stderr: os
- name: service-two
inheritEnv: true
env:
- name: VAR_3
value: VAL_3
- name: VAR_EMPTY_QUOTE
value: ""
watch:
fs:
paths:
- cmd/service-two/main.go
run: go run cmd/service-two/main.go
output:
stdout: os
stderr: osSchema (inferred from code):
- Service fields (
internal/service/service.go):name(string) — requiredrun(string) — required; shell command to start the servicebefore(string) — optional; one-time command executed prior to first startwatch(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) — when set toos, stdout is passed through to the terminalstderr(string) — when set toos, stderr is passed through to the terminalstdin(string) — when NOT set toos, stdin is passed through to the terminal (current behavior in code)
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
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 (from original TODO) - Document Windows support and SIGINFO behavior across platforms
- Add unit and integration tests
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 ├── 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 └── LICENSEThere 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.