[!CAUTION]
This repository is in beta and not stable enough since the design space of
this tool is still explored.
The quitsh framework (/kwɪʧ/) is a build-tooling CLI framework designed to
replace loosely-typed scripting languages (e.g., bash, python, and similar
alternatives) with the statically-typed language Go. Its goal is to simplify
tooling tasks while providing robust, extendable solutions for component
repositories (mono-repositories).
quitsh is an opinionated framework born out of frustration with the lack of
simple and extendable tooling for mono-repos. It is language-agnostic and
toolchain-independent, allowing users to focus on their workflows without being
constrained by specific technologies.
Key Features
Code-First Approach
- All tooling logic is implemented in
Go.
- Tasks are defined primarily in code, avoiding declarative configurations or
templated non-typed languages, which often add unnecessary complexity despite
their flexibility.
Component Identification
- Components (i.e., buildable units) are identified by placing a configuration
file (default:
.component.yaml) in the corresponding subdirectory.
Extendability
quitsh serves as a library to build your customized CLI tool for your
specific tasks.
- Users can add custom commands and specialized tooling features using libraries
like
cobra.
Targets and Steps
- Each component defines targets, which consist of multiple steps.
- Targets can depend on other targets across the repository.
- Input change sets can be specified for each target to track modifications and
determine if the target is outdated.
Runner System
- Steps within targets are executed by runners, which are written by you in
Go and act as reusable replacements for traditional build/tooling scripts.
- Runners can have custom YAML configuration options specified per component in
.component.yaml.
-
Runners are associated with specific toolchains.
-
By default, quitsh includes a
Nix development shell dispatch,
providing stable and reproducible environments.
-
While container-based dispatching is not a primary goal, it can be implemented
by extending the dispatch interface.
-
The tool was built to replicate the same procedure one executes during local
development and also in CI. Having CI align with what you execute locally is
not a nice thing to have, its a necessity. Nix development shells (or
containers) help with this. A Nix shell provides a simple and robust
abstraction to pin a toolchain. The following visualization gives an overview
about how quitsh is used:

Built-in Libraries
The pkg folder offers utilities for common development needs, such as:
- Command Execution:
pkg/exec provides utilities for process
execution and command chaining.
- Structured Logging:
pkg/log enables consistent and readable
logging.
- Error Handling:
pkg/error facilitates contextual error
management.
- Dependency Graphs: Tools for managing and resolving dependency graphs across
targets.
- Some Go
test runners (here as an example) for running Go tests (its used
internally to test quitsh it-self).
- Since all tooling is written in
Go, quitsh provides type safety and fast
performance by default.
- Combined with Nix-based toolchain dispatch and the ability to write tests
easily, the framework significantly accelerates the "change, test, improve"
workflow.
Nix Integration
- CLI tools built with
quitsh can be seamlessly packaged into Nix development
shells, ensuring accessibility for all users of a given repository.
How We Use It?
TODO: unfinished.
Understand what this framework does, is best accomplished by understanding how
we use this framework in our
components repo (mono-repo).
Components
Our major components are located in
./components.
Each component in quitsh is defined by a .component.yaml (name is
customizable) file which more or less looks like:
# The name of the component: Must be a unique.
name: my-component
# A semantic version.
# This is useful for Nix packaging etc.
version: 0.2.1
# A simple annotation (not used internally) what main language this component uses.
language: go
targets:
# A target called `test` with two steps.
my-test:
# The stage to which this target belongs. Does not need to be provided
# if the CLI is setup to map target names to stages.
stage: test
steps:
# Step 1: Using runner with ID (how it was registered).
- runner-id: banana-project::my-test-runner
config: # Your custom runner YAML config, (optional).
# Step 2: Using a runner with registered key (stage: `test`, name `my-test`)
- runner: my-test
# A target called `build-all` with one step.
build-all:
stage: build
# Defining when this target is considered changed:
# i.e. whenever `self::sources` input change set is changed.
inputs: ["self::sources"]
# Defining dependencies on other targets such that this
# target is executed after target `my-test` above.
# You can also link to other components (e.g `other-comp::build`).
depends: ["self::my-test"]
steps:
# Step 1: Using a runner with registered key (stage: `build`, name `my-test`)
- runner: my-build
config:
tags: ["doit"]
lint:
steps:
- ... other steps ...
inputs:
# An input change set with name `sources` which defines
# patterns to match all source files.
sources:
# A regex which matches `*.go` files in `./src` in the components folder.
patterns:
- '^./src/.*\.go$'