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 monochange step <kebab-step-name>, for example monochange step discover, monochange step prepare-release, and monochange 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 monochange run <command>. monochange 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 (Config, Validate, Discover, AffectedPackages, DiagnoseChangesets, RetargetRelease, VerifyReleaseBranch, ReleaseRecord, PublishReadiness, and TagRelease). 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 monochange run <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", "json-min"], 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", "json-min", "markdown"], default = "text" },
	{ 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.

Override values in the map form accept native TOML literals: strings, booleans, integers, and floats. Booleans stay booleans in the parsed model and are stringified to "true"/"false" when the step runs; numbers are coerced to their string form at parse time, so writing { jobs = 4, ratio = 2.5 } is exactly the same as writing { jobs = "4", ratio = "2.5" }:

[[cli.release.steps]]
name = "publish"
type = "Command"
command = "npm publish --jobs {{ inputs.jobs }}"
inputs = { jobs = 4, dry_run = true }

Interactive command steps

Add an explicit interactive boolean input to the command, pass it to a Command step, and run the workflow with --interactive when you want the command to own the terminal. The step inherits stdio for that run, so prompts and terminal UIs work, and the progress spinner is suppressed while the command runs.

[cli.publish]
help_text = "Publish packages"

[[cli.publish.inputs]]
name = "interactive"
type = "boolean"
default = false

[[cli.publish.steps]]
name = "publish"
type = "Command"
command = "npm publish"
inputs = ["interactive"]
monochange run publish --interactive

Leave interactive unset (the default) for CI and scripted runs so those commands stay non-interactive. Interactive steps do not capture output: steps.<id>.stdout and steps.<id>.stderr are empty for them, so downstream steps cannot read what an interactive command printed.

Built-in monochange step <name> 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
Configinspect resolved configuration and workspace metadatanolocal debugging, CI artifacts
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
ReleaseRecordinspect an embedded release record from a commit or tagrelease commit or tagtag repair, publish readiness, custom auditing
VerifyReleaseBranchverify a ref is reachable from configured release branches[source.releases]early release CI gates; enforced internally by tag and publish paths
TagReleasecreate and optionally push release tags from an embedded release recordrelease commitsource-provider releases, package publishing, custom announcements
PublishReadinesscheck package-registry readiness without publishing packagesrelease commit or tagPlanPublishRateLimits, human review, package publishing
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 without changing execution; use --dry-run separately
  • 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 release workflows such as monochange step prepare-release --dry-run and configured monochange run release wrappers.

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

Every step reports its start and terminal status by default, including interactive steps. Animation remains terminal-only, and step kinds that support it can set show_progress = false to suppress their 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