Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CLI step reference

monochange CLI commands are built in two layers:

  • immutable built-in step commands: every built-in step except Command is exposed directly as mc step:<kebab-step-name>, for example mc step:discover, mc step:prepare-release, and mc step:affected-packages. These commands are generated by the binary, derive their flags from the step schema, and do not require a [cli.*] entry in monochange.toml.
  • config-driven workflow commands: every [cli.<command>] table in monochange.toml becomes mc <command>. mc init does not seed default workflow aliases; add these tables when you want a named workflow that chains steps, adds custom inputs, or runs Command steps.

A step is the smallest execution unit in a monochange workflow. Some steps are standalone (Validate, Discover, AffectedPackages, DiagnoseChangesets, RetargetRelease, VerifyReleaseBranch). Others are stateful and build on the result of an earlier PrepareRelease step (CommitRelease, PublishRelease, OpenReleaseRequest, and CommentReleasedIssues). PrepareRelease also refreshes the cached .monochange/release-manifest.json artifact exposed to later steps as manifest.path.

When you design a command, think in terms of:

  1. what state the command needs
  2. which step produces that state
  3. which later step consumes it
  4. what side effects are acceptable in normal mode vs --dry-run

The reference pages in this section document each built-in step with:

  • what the step does
  • why you would choose it over a shell-only Command
  • which inputs it accepts
  • what prerequisite state it needs
  • what it contributes to later steps
  • examples of how it composes into full workflows

Explicit step input inheritance

Config-defined workflow commands have two input layers:

  1. [[cli.<command>.inputs]] declares the flags and arguments accepted by mc <command>.
  2. inputs on each step decides which of those parsed command inputs are visible while that step runs.

Command inputs are not inherited automatically. A step receives a command input only when the step explicitly lists it. This makes wrappers predictable when a command-level flag and a step-specific input share the same name.

Use the array shorthand when a step should inherit command inputs unchanged:

[cli.discover]
inputs = [
	{ name = "format", type = "choice", choices = ["text", "json"], default = "text" },
]
steps = [
	{ type = "Discover", inputs = ["format"] },
]

Use the map form when a step needs fixed values, renamed values, templates, or a mixture of inherited and overridden values:

[cli.release-pr]
inputs = [
	{ name = "format", type = "choice", choices = ["text", "json", "markdown"], default = "markdown" },
	{ name = "open_as_draft", type = "boolean", default = false },
]
steps = [
	{ type = "PrepareRelease", inputs = ["format"] },
	{ type = "OpenReleaseRequest", inputs = { format = "markdown", draft = "{{ inputs.open_as_draft }}" } },
]

Step-local when expressions and command templates evaluate against the same explicit step input context. If a when condition references inputs.publish, the step must include publish in its inputs array or map. Use inputs = ["publish"] for unchanged inheritance, or inputs = { publish = "{{ inputs.publish }}" } when you need the map form for other overrides.

Built-in mc step:* commands are different: they are generated directly from the step schema, so their CLI flags map to that single step without a [cli.*] wrapper.

Choosing the right step

StepUse it when you want to…Requires previous step?Typical follow-up
Validatefail fast on invalid config, groups, or changesetsnoCI gate or local preflight
Discoverinspect normalized package discovery across ecosystemsnolocal inspection, debug commands
CreateChangeFileauthor a .changeset/*.md file from CLI inputsnorun independently, or before planning
PrepareReleasebuild the release result, update files, and refresh the cached manifestnoCommitRelease, PublishRelease, OpenReleaseRequest, CommentReleasedIssues, Command
DisplayVersionsdisplay planned package and group versions without mutating release filesnoPrepareRelease
CommitReleasecreate a local release commit with an embedded ReleaseRecordPrepareReleaseOpenReleaseRequest, manual review, custom Command
VerifyReleaseBranchverify a ref is reachable from configured release branches[source.releases]early release CI gates; enforced internally by tag and publish paths
PublishReleasecreate or update hosted provider releasesPrepareRelease + [source]CommentReleasedIssues, custom notification commands
OpenReleaseRequestcreate or update a hosted release PR/MRPrepareRelease + [source]provider review, follow-up Command steps
PlanPublishRateLimitsplan package-registry publish work against known rate limitsnoPublishPackages, PlaceholderPublish
PlaceholderPublishpublish 0.0.0 placeholder versions for missing registry packagesnonormally before PublishPackages
PublishPackagespublish package versions to registries using built-in ecosystem workflowsprepared or HEAD release statecustom Command steps using publish.*
CommentReleasedIssuespost release follow-up comments to closed issuesPrepareRelease + GitHub sourcenormally after PublishRelease
AffectedPackagesevaluate changeset coverage for changed filesnoCI enforcement, custom failure messaging
DiagnoseChangesetsinspect changeset context, commit provenance, and linked review metadatanolocal debugging, CI inspection
RetargetReleaserepair a recent release by moving its tag setnocustom Command steps using retarget.*
Commandrun arbitrary shell/program commands with monochange contextdepends on your workflowany external tool

A note on composition

monochange executes steps in order.

That means composition is explicit:

  • a step can only consume state created by an earlier step in the same command
  • a later step never runs “in parallel” with an earlier one
  • --dry-run flows through the whole command and changes the behavior of steps that support previews
  • --quiet suppresses stdout/stderr and reuses dry-run behavior for commands that support it
  • a plain Command step can bridge monochange and external tools, but built-in steps are preferable when you want stable semantics, structured JSON, or provider-aware behavior

In practice, most workflows fit one of four patterns:

  1. validation / inspection
    • Validate
    • Discover
    • DisplayVersions
    • AffectedPackages
    • DiagnoseChangesets
  2. change authoring
    • CreateChangeFile
  3. release preparation and publication
    • PrepareRelease
    • then one or more of CommitRelease, PublishRelease, OpenReleaseRequest, CommentReleasedIssues, Command
  4. post-release repair
    • RetargetRelease
    • optionally followed by Command

Shared concepts

Step-local name

Every step can declare a name = "..." label.

Use that when you want human-friendly progress output such as plan release, publish tags, or announce release instead of the raw step kind.

Progress rendering

monochange can stream step progress on stderr while keeping command output on stdout.

Use --progress-format auto|unicode|ascii|json or MONOCHANGE_PROGRESS_FORMAT to choose the renderer:

  • auto enables the human renderer only when stderr is a terminal
  • unicode forces the human renderer with Unicode symbols
  • ascii forces the human renderer with ASCII-safe symbols
  • json emits newline-delimited progress events for automation and benchmarks

PrepareRelease and DisplayVersions steps also report per-phase timings when they compute release state. Those timings power the benchmark phase-budget checks for mc release --dry-run and mc release.

See Progress output for the full renderer behavior and JSON event shape.

Step-local when

Every step can declare a when = "..." expression.

It uses minijinja-style expression evaluation with template context and supports logical combinations like and, or, and not (for example: "{{ inputs.publish && !inputs.dry_run }}").

If the expression resolves to false, monochange skips that step and continues with the next step. Falsy values include false, 0, and the empty string.

Step-local always_run

Every step can declare always_run = true.

When a previous step in the same command fails, monochange normally aborts and returns the error. Steps marked with always_run = true are the exception: they still execute even after an earlier step has failed.

Use this for cleanup, notification, or dry-run preview steps that should run regardless of whether earlier work succeeded.

Step-local inputs

Every step can define an inputs = { ... } override inside the step table.

Use that when:

  • a command-level input should be rebound to a built-in step input
  • you want to hardcode a value for one step but not the entire command
  • you want to pass list or boolean values through direct template references such as "{{ inputs.changed_paths }}"

Step-local show_progress

Interactive steps do not show the spinner by default when monochange is waiting on the user. For step kinds that support it, you can also set show_progress = false to suppress progress output explicitly.

Structured template namespaces

When you compose Command steps after built-in steps, monochange exposes structured context values such as:

  • release.* after PrepareRelease
  • manifest.path after PrepareRelease
  • affected.* after AffectedPackages
  • retarget.* after RetargetRelease
  • release_commit.* after CommitRelease
  • steps.<id>.stdout and steps.<id>.stderr after a Command step with id = "..."

Those namespaces are the main reason to prefer built-in steps over reimplementing the same workflow in shell.

Pages in this section