PublishPackages
What it does
PublishPackages publishes package versions to their target registries using monochange’s built-in ecosystem workflows.
The step derives publish work from durable monochange release state: a prepared release artifact when the command has one, or the release record discoverable from HEAD otherwise. It does not require a readiness artifact. Before publishing, it orders selected package publications by internal publish-relevant dependencies so dependencies are attempted before dependents. Runtime, build, peer, workspace, and unknown dependency kinds participate in ordering and cycle validation; development-only dependency cycles are ignored.
For each package with a planned release version, the step:
- resolves the registry from the package’s publish configuration
- validates publish-relevant dependency cycles before registry mutation
- publishes dependencies before dependents within the selected publish set
- checks whether the version already exists (skipping if it does)
- plans against registry rate limits before attempting any mutation
- runs the ecosystem-specific publish command (
cargo publish,npm publish,dart pub publish,flutter pub publish,deno publish, and so on) - produces a structured report of what was published, skipped, or planned
You can filter the publish set with the package input, or use an empty set to publish everything from the selected release state.
Publication order
Package publication order is dependency-aware. monochange publishes packages with no selected dependencies first, then publishes packages that depend on those packages, walking up the dependency tree until packages that depend on the most selected packages are published last.
The order is computed like this:
- Build the selected publish requests from the prepared release or
HEADrelease state. - Materialize the workspace dependency graph.
- Consider only dependencies where both packages are part of the selected publish set.
- Ignore development dependency edges.
- Topologically sort the publish requests so dependencies are emitted before dependents.
For example, with this internal package graph:
core # no dependencies
utils # depends on core
api # depends on utils
app # depends on core, utils, api
monochange publishes in this order:
core
utils
api
app
If multiple packages are independent at the same depth, their order is deterministic by package id, registry, and version.
A package with no selected dependencies is eligible first. A package is not published until all of its selected publish-relevant dependencies have been ordered before it. Dependencies outside the selected publish set do not block ordering. Development-only cycles are ignored. Runtime, build, peer, workspace, and unknown dependency cycles fail before publishing anything, with a cycle diagnostic.
Why use it
Use PublishPackages when you want monochange to handle the full package-registry publication workflow rather than scripting individual publish commands.
That gives you:
- one publish step for all supported ecosystems
- automatic dependency ordering across internal package publications
- publish-relevant cycle detection before registry mutation
- automatic rate-limit planning and enforcement
- version-existence checks that prevent duplicate publish attempts
- dry-run previews that show the full publish plan without touching registries
- structured
publish.*template context for laterCommandsteps
Use PlaceholderPublish instead when you need to bootstrap a package that does not yet exist in its registry with a minimal 0.0.0 placeholder.
Inputs
format—text,markdown, orjsonpackage— optional repeated package ids used to filter the publish setgroup— optional repeated group ids; all packages in each group are added to the publish setecosystem— optional repeated ecosystem names (cargo,npm,deno,dart,flutter,python,go); only packages targeting the selected ecosystems are publishedresume— optional path to a JSON result artifact from an earlier realmc publishrun; completed package versions are skipped and failed or pending work is retriedoutput— optional path where monochange writes the package publish result JSON artifact for retry/resume workflows
Step-level when condition
All CLI steps support an optional when = "..." condition.
If the expression resolves to false at runtime, monochange skips the step and continues with the next step.
when = "{{ inputs.enabled }}"
Step-level always_run flag
All CLI steps support an optional always_run = true flag.
When set, the step executes even if a previous step in the same command has failed. This is useful for cleanup, notification, or dry-run preview steps that must run regardless of earlier outcomes.
always_run = true
Prerequisites
- a prepared release artifact or a release record discoverable from
HEADthat contains the package publication targets - no cycles among selected publish-relevant internal dependencies; development-only cycles are allowed
- for built-in Cargo publishes to crates.io, a publishable current
Cargo.toml: nopublish = false, anypublish = [...]list includescrates-io,descriptionis set, and eitherlicenseorlicense-fileis set; workspace-inherited values are accepted
Side effects and outputs
- in dry-run mode, plans and previews publish operations without touching registries
- in normal mode, validates release-branch policy and publish-relevant dependency cycles, then publishes package versions to their configured registries
- when
outputis set, writes the package publish result artifact even if a registry publish command fails, then exits non-zero for failed package outcomes - contributes
publish.*andpublish_rate_limits.*template context to the command result
Example
[cli.publish]
help_text = "Publish package versions from monochange release state using built-in workflows"
[[cli.publish.inputs]]
name = "format"
type = "choice"
choices = ["text", "markdown", "json"]
default = "text"
[[cli.publish.inputs]]
name = "package"
type = "string_list"
[[cli.publish.inputs]]
name = "group"
type = "string_list"
help_text = "Group ids whose member packages should be published"
[[cli.publish.inputs]]
name = "ecosystem"
type = "string_list"
help_text = "Ecosystems to publish (cargo, npm, deno, dart, flutter, python, go)"
[[cli.publish.inputs]]
name = "resume"
type = "path"
help_text = "JSON result artifact from an earlier mc publish run; completed package versions are skipped"
[[cli.publish.inputs]]
name = "output"
type = "path"
help_text = "Write the package publish result JSON artifact for retry/resume"
[[cli.publish.steps]]
name = "publish packages"
type = "PublishPackages"
inputs = ["format", "package", "group", "ecosystem", "resume", "output"]
Composition ideas
Preview readiness before publishing
Use mc step:publish-readiness when you want a reviewable preflight report, then publish directly from the same release state:
mc step:publish-readiness --from HEAD --output .monochange/readiness.json
mc publish --output .monochange/publish-result.json
The readiness artifact is informational for PublishPackages; it is not required by mc publish. If a real publish fails after writing .monochange/publish-result.json, fix the registry/auth issue and rerun with mc publish --resume .monochange/publish-result.json --output .monochange/publish-result.json.
Publish only a specific package
[cli.publish-core]
help_text = "Publish a specific package"
[[cli.publish-core.inputs]]
name = "package"
type = "string_list"
required = true
[[cli.publish-core.steps]]
name = "publish packages"
type = "PublishPackages"
Publish with rate-limit planning
[cli.publish-planned]
help_text = "Plan and publish with rate-limit awareness"
[[cli.publish-planned.steps]]
name = "plan publish rate limits"
type = "PlanPublishRateLimits"
[[cli.publish-planned.steps]]
name = "publish packages"
type = "PublishPackages"
Why choose it over a raw Command step?
Because PublishPackages understands:
- which packages were planned for release
- which ecosystem and registry each package targets
- which selected internal packages must publish before others
- whether publish-relevant dependency cycles would make a safe order impossible
- whether a version already exists (and should be skipped)
- ecosystem-specific publish commands, flags, and auth patterns
- rate-limit planning across registries
- dry-run behavior for safe CI previews
- trusted publishing setup and configuration
Common mistakes
- confusing
PublishPackageswithPublishRelease: the former publishes to package registries, the latter creates hosted provider releases (such as GitHub releases) - assuming
mc publishconsumes the JSON file frommc step:publish-readiness; use readiness for preflight review ormc publish-plan --readiness, not as aPublishPackagesinput - omitting
outputin CI, which makes partial registry failures harder to resume safely - expecting development-only dependency cycles to block publishing; only publish-relevant dependency kinds participate in cycle validation
- running
PublishPackageswithout rate-limit planning: usePlanPublishRateLimitsfirst when you are unsure about registry windows