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

Your first release plan

Use this guide after installation when you want one local, beginner-safe walkthrough.

You will stop at mc release --dry-run --format json, so nothing is published.

1. Generate a starter config with mc init

Run this at the repository root:

mc init

mc init detects packages, writes an annotated monochange.toml, and gives you a better starting point than hand-authoring a first config from scratch.

The generated file is intentionally minimal and does not create default [cli.*] workflow aliases. Every built-in step is available directly as an immutable mc step:* command, for example mc step:discover, mc step:create-change-file, and mc step:prepare-release.

Add [cli.*] tables only when you want repository-specific named workflows that chain steps, expose custom inputs, or run shell Command steps.

Automated CI setup with --provider

When you know which source provider you will use for release automation, include the --provider flag during initialization:

mc init --provider github

The --provider flag supports github, gitlab, and gitea. When provided, mc init:

  1. Configures the [source] section — adds provider-specific settings for releases and pull/merge requests
  2. Generates provider CLI commands — includes commit-release and release-pr commands in monochange.toml
  3. Creates workflow files (GitHub only) — writes .github/workflows/release.yml and .github/workflows/changeset-policy.yml
  4. Auto-detects owner/repo — parses git remote get-url origin to pre-populate [source]

Example generated configuration with --provider github:

[source]
provider = "github"
owner = "ifiokjr" # auto-detected from git remote
repo = "monochange" # auto-detected from git remote

[source.releases]
enabled = true
draft = false
prerelease = false
source = "monochange"

[source.releases]
branches = ["main", "release/*"]
enforce_for_tags = true
enforce_for_publish = true
enforce_for_commit = false
changeset_context_timeout_seconds = 120

[source.pull_requests]
enabled = true
branch_prefix = "monochange/release"
base = "main"
title = "chore(release): prepare release"
labels = ["release", "automated"]
auto_merge = false

[cli.commit-release]
help_text = "Prepare a release and create a release commit"

[[cli.commit-release.steps]]
type = "PrepareRelease"
name = "plan release"

[[cli.commit-release.steps]]
type = "CommitRelease"
name = "create release commit"

[cli.release-pr]
help_text = "Prepare a release and open a release pull request"

[[cli.release-pr.steps]]
type = "PrepareRelease"
name = "plan release"

[[cli.release-pr.steps]]
type = "OpenReleaseRequest"
name = "open release PR"

The GitHub Actions workflows enable:

  • Release automationrelease.yml refreshes the release PR on normal main pushes, then tags and publishes when the merged release commit lands on main
  • Changeset policy enforcementchangeset-policy.yml validates PRs have required changeset coverage

For GitLab and Gitea, the [source] section is configured but workflows are not generated (use their respective CI configuration files).

2. Validate the generated workspace

mc step:validate

This confirms that the generated config and any existing .changeset/*.md files agree with the workspace.

If validation fails, fix the reported problem first, then rerun mc step:validate.

3. Discover the package ids you will actually use

mc step:validate
mc discover --format json

The most important thing to find in discovery output is the package id you want to target in your first change file.

If you are unsure what id to use later, rerun discovery and copy one from the output.

4. Create one change file

mc change --package <id> --bump patch --reason "describe the change"

Most changes should target a package id.

monochange will propagate bumps to dependents and synchronize configured groups for you, so group ids are best reserved for intentionally shared ownership.

5. Preview the release plan safely

mc release --dry-run --format json

This is the right stopping point for a first-time user.

You get a concrete preview of the release plan without publishing anything or opening provider requests.

When you are ready to move beyond planning:

  • use mc placeholder-publish --dry-run --format json if some packages still need a bootstrap 0.0.0 release so they exist in their registries first
  • use mc publish --dry-run --format json to preview built-in package publication to crates.io, npm, jsr, or pub.dev
  • before real package publication, optionally write a readiness artifact with mc step:publish-readiness --from HEAD --output .monochange/readiness.json for preflight review, then run mc publish
  • use mc publish-release --dry-run --format json only for hosted/provider releases such as GitHub releases

Package ids vs. group ids

Use this rule of thumb:

  • package ids first — most authored changes belong to one package
  • group ids later — use a group id only when the change is intentionally owned by the whole group

That keeps your first changes simple while still letting monochange synchronize grouped packages when needed.

First-failure recovery

mc init says a config already exists

Keep the existing monochange.toml, inspect it, and continue with mc step:validate. If you want to regenerate the config from scratch, pass the --force flag:

mc init --force

mc step:validate reports config or changeset errors

Fix the reported issue first. mc step:validate is the fastest way to get back to a known-good workspace.

mc change says the package id is unknown

Run mc discover --format json again and copy an id directly from the output.

You are not ready to hand-edit config yet

That is normal. Stay with the generated monochange.toml until the basic flow feels familiar.

When to edit monochange.toml by hand

Most first-time users should not start by writing a large config manually.

Reach for manual edits when you want to:

  • rename or reorganize package ids
  • define groups with [group.<id>]
  • customize changelog paths or formats
  • add provider configuration for release publishing or release PRs
  • expand the CLI surface beyond the default generated commands

Reference: expanded configuration example

The example below shows the broader package, group, changelog, source-provider, and CLI-command model.

Use it as reference material after the generated config makes sense.

[defaults]
parent_bump = "patch"
warn_on_group_mismatch = true
package_type = "cargo"

[defaults.changelog]
path = "{{ path }}/changelog.md"
format = "keep_a_changelog"

[release_notes]
change_templates = [
	"#### {{ summary }}\n\n{{ details }}\n\n{{ context }}",
	"#### {{ summary }}\n\n{{ context }}",
	"#### {{ summary }}\n\n{{ details }}",
	"- {{ summary }}",
]

[package.sdk-core]
path = "crates/sdk_core"
extra_changelog_sections = [
	{ name = "Security", types = ["security"], default_bump = "patch" },
]

[package.web-sdk]
path = "packages/web-sdk"
type = "npm"

[package.mobile-sdk]
path = "packages/mobile-sdk"
type = "dart"

[group.sdk]
packages = ["sdk-core", "web-sdk", "mobile-sdk"]
tag = true
release = true
version_format = "primary"

[group.sdk.changelog]
path = "docs/sdk-changelog.md"
format = "monochange"

[source]
provider = "github"
owner = "ifiokjr"
repo = "monochange"

[source.releases]
source = "monochange"

[source.pull_requests]
branch_prefix = "monochange/release"
base = "main"
title = "chore(release): prepare release"
labels = ["release", "automated"]
auto_merge = false

[changesets.affected]
enabled = true
required = true
skip_labels = ["no-changeset-required"]
comment_on_failure = true
changed_paths = ["crates/**", "packages/**", "npm/**", "skills/**"]
ignored_paths = [
	"docs/**",
	"specs/**",
	"readme.md",
	"CONTRIBUTING.md",
	"license",
]

name = "production"
trigger = "release_pr_merge"
release_targets = ["sdk"]
requires = ["main"]

[cli.discover]
help_text = "Discover packages across supported ecosystems"

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

[[cli.discover.steps]]
name = "discover packages"
type = "Discover"
inputs = ["format"]

[cli.change]
help_text = "Create a change file for one or more packages"

[[cli.change.inputs]]
name = "interactive"
type = "boolean"
short = "i"

[[cli.change.inputs]]
name = "package"
type = "string_list"

[[cli.change.inputs]]
name = "bump"
type = "choice"
choices = ["none", "patch", "minor", "major"]
default = "patch"

[[cli.change.inputs]]
name = "version"
type = "string"

[[cli.change.inputs]]
name = "reason"
type = "string"

[[cli.change.inputs]]
name = "type"
type = "string"

[[cli.change.inputs]]
name = "details"
type = "string"

[[cli.change.inputs]]
name = "output"
type = "path"

[[cli.change.steps]]
name = "create change file"
type = "CreateChangeFile"
inputs = ["interactive", "package", "bump", "version", "type", "reason", "details", "output"]

[cli.release]
help_text = "Prepare a release from discovered change files"

[[cli.release.inputs]]
name = "format"
type = "choice"
choices = ["text", "json"]
default = "text"

[[cli.release.steps]]
name = "prepare release"
type = "PrepareRelease"
inputs = ["format"]

[cli.publish-release]
help_text = "Prepare a release and publish provider releases"

[[cli.publish-release.inputs]]
name = "format"
type = "choice"
choices = ["text", "json"]
default = "text"

[[cli.publish-release.steps]]
name = "prepare release"
type = "PrepareRelease"
inputs = ["format"]

[[cli.publish-release.steps]]
name = "publish release"
type = "PublishRelease"
inputs = ["format"]

[cli.release-pr]
help_text = "Prepare a release and open or update a provider release request"

[[cli.release-pr.inputs]]
name = "format"
type = "choice"
choices = ["text", "json"]
default = "text"

[[cli.release-pr.steps]]
name = "prepare release"
type = "PrepareRelease"
inputs = ["format"]

[[cli.release-pr.steps]]
name = "open release request"
type = "OpenReleaseRequest"
inputs = ["format"]

[cli.affected]
help_text = "Evaluate pull-request changeset policy"

[[cli.affected.inputs]]
name = "format"
type = "choice"
choices = ["text", "json"]
default = "text"

[[cli.affected.inputs]]
name = "changed_paths"
type = "string_list"
required = true

[[cli.affected.inputs]]
name = "label"
type = "string_list"

[[cli.affected.steps]]
name = "evaluate affected packages"
type = "AffectedPackages"
inputs = ["format", "changed_paths", "label"]

This guide shows the preferred package/group configuration model together with an expanded CLI command surface.