Metadata-Version: 2.4
Name: commoner-cli
Version: 0.1.0
Summary: One front door for a repo's entry points: tests, processes, docker and migrations, defined in a commoner.yaml instead of a pile of shell scripts.
License-Expression: MIT
License-File: LICENSE
Keywords: cli,developer-experience,monorepo,task-runner,scripts,docker,pytest
Author: matthew
Author-email: beattyml1@gmail.com
Requires-Python: >=3.11
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Utilities
Requires-Dist: pyyaml (>=6.0)
Project-URL: Homepage, https://github.com/beattyml1/commoner-cli
Description-Content-Type: text/markdown

# commoner

One front door for everything a repo can do. Test tiers, dev servers, the docker
stack, migrations and the little chains of steps between them are named in a
`commoner.yaml` — and `commoner <thing>` runs them.

The alternative it replaces is a `scripts/` folder: fifteen four-line shell
files that each `cd` to the repo root and exec one command, where adding a test
tier means writing another one and the only way to know what a repo can do is to
`ls` them.

```bash
pip install commoner-cli
```

```yaml
# commoner.yaml
tests:
  unit: {marker: unit}
  e2e:
    marker: e2e
    args: --api-mode=live_server

ui:
  path: ui
  package_manager: pnpm
  tests:
    unit: {script: test}

run:
  api:
    script: uvicorn app.api:app --reload --port 8000

docker:
  path: infrastructure
  local:
    compose: compose.yaml
    profiles: {api: the API in a container}

migration-providers: [alembic]

tasks:
  init-db:
    steps: [up --detach, migrate]
```

```bash
commoner help                # what this repo defines — generated from the config
commoner test unit           # poetry run pytest -m unit
commoner test ui:unit        # pnpm run test, in ui/
commoner test                # everything, across every project
commoner api                 # poetry run uvicorn app.api:app --reload --port 8000
commoner up api --detach     # docker compose --profile api up --detach
commoner init-db             # compose up --detach, then alembic upgrade head
commoner --dry-run <any>     # print the commands, run nothing
```

Nothing above is built in. `unit`, `api`, `init-db` and the rest exist because
the config names them; the tool only knows how to run a *kind* of thing.

## The config

Top-level keys are either a known section or a **project** — `ui:` is a project
because it isn't reserved. `example.commoner.yaml` is the annotated schema; the
short version:

| Section | What it defines |
| --- | --- |
| `project` | the repo's name, for help output |
| `python` | the root project's toolchain: `runner` (poetry/uv/none), `install_args`, `tests_path` |
| `tests` | test tiers, as `commoner test <tier>` |
| `run` | long-lived processes; each is also a top-level command |
| `docker` | one compose file and its profiles, as `commoner up [profile...]` |
| `migration-providers` | gives you `commoner migrate` / `commoner revision` |
| `tasks` | a named sequence of other commoner commands |
| `env` | the env file `commoner bootstrap` creates from its example |
| anything else | another project, with its own path and package manager |

### What an entry runs

Every entry — a tier, a process, a step — says what to run in exactly one of
four ways, and where with `project:`:

```yaml
unit:    {path: unit}                # <runner> pytest tests/unit
unit:    {marker: unit}              # <runner> pytest -m unit    (module: also works)
unit:    {script: pytest -m unit}    # through the project's runner / package manager
unit:    {cmd: poetry run pytest}    # literally, as written
```

Plus `args:` (always appended, before the caller's own), `description:` (shown
in help), and `todo:` (say it isn't built yet and skip, rather than fail).

Extra arguments on the command line are appended to whatever the entry resolves
to, so the `"$@"` you would write in a shell script is implicit — and stripped
if you write it anyway. `commoner test e2e -k login` and
`commoner test ui:e2e --tags '@auth'` both land where you expect.

### Conventions worth knowing

- A tier named `all` stands in for its whole project, so `commoner test` is one
  `pytest` run rather than one per marker.
- A node project with no `node_modules` makes its tiers **skip**, not fail — a
  partial checkout stays testable.
- Unprofiled compose services always come up; profiles layer the rest on top.
  `commoner up` is your backing services, `commoner up api workers` the lot.
- Names in `run:` and `tasks:` become top-level commands, and the config is
  rejected if one shadows a built-in.

## Built-in commands

| Command | What it does |
| --- | --- |
| `bootstrap` | install every project's dependencies, create the env file |
| `test [tier] [args...]` | run a tier, a project's tiers, or everything |
| `run <name> [args...]` | run a configured process |
| `up [profile...]` / `down` | the docker stack |
| `build` | build the configured dockerfile |
| `compose <args...>` | raw docker compose against the stack |
| `migrate [target]` / `revision <message>` | the configured migration provider |
| `help` | the repo's own command surface |

Global flags: `--config PATH`, `--dry-run`, `-q/--quiet`, `--version`.

## How it works

- `config.py` — the only module that knows the file format: loading,
  validation, and turning an entry into an argv.
- `cli.py` — the dispatcher. Help text is generated from the config, so it
  can't drift from it.
- `process.py` — how a command leaves the process: `exec_` hands over the
  terminal (signals, exit codes and Ctrl-C belong to the real command), `run`
  waits. A multi-step caller sets `Console.replace = False`, which turns the
  former into the latter for everything nested under it — so a command written
  to hand over still composes inside a task.
- `commands/` — one module per built-in; `migrations/` — one per provider.

A project is a unit of source with its own toolchain: it knows how to install
itself, how to prefix a command so it runs in its environment, and when it isn't
ready.

## Wiring it into a repo

Installing puts `commoner` on your PATH. Many repos also keep a one-line `dev`
shim at the root so a fresh clone needs no explanation:

```bash
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
exec commoner "$@"
```

## Status

Early. The schema is settled enough to use and documented in
`example.commoner.yaml`; `alembic` is the only migration provider so far, and
new *kinds* of thing are the only reason to change the code — a new entry point
is a yaml edit.

MIT licensed.

