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 monochange run release --dry-run --format json, so nothing is published.

1. Generate a starter config with monochange init

Run this at the repository root:

monochange init

monochange 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 monochange step * command, for example monochange step discover, monochange step create-change-file, and monochange 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.

Commit .monochange/, gitignore only .monochange/local/

monochange writes committed state under .monochange/. The most important file is the release record at .monochange/releases/<id>/release.json, which CommitRelease writes and later publish-readiness, tag-release, and provider release automation read from git history. Prerelease state in .monochange/prerelease-state.json is committed too.

Only the .monochange/local/ directory holds local artifacts. Never add .monochange/ as a whole to .gitignore: ignoring it hides new release records from git and makes releases unpublishable. monochange keeps local state out of git status by adding .monochange/local/ to .git/info/exclude automatically when it writes a local artifact, so local runs should either use the default .monochange/local/ paths or write artifacts to a temporary directory.

Automated CI setup with --provider

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

monochange init --provider github

The --provider flag supports github, gitlab, and gitea. When provided, monochange 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"
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 automation - release.yml refreshes the release PR on normal main pushes, then tags and publishes when the merged release commit lands on main
  • Changeset policy enforcement - changeset-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

monochange 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 monochange step validate.

3. Discover the package ids you will actually use

monochange step 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

monochange run 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

monochange run 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 monochange step 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 monochange step publish-packages --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 monochange step publish-readiness --from HEAD --output .monochange/readiness.json for preflight review, then run monochange step publish-packages
  • use monochange step 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

monochange init says a config already exists

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

monochange init --force

monochange step validate reports config or changeset errors

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

monochange run change says the package id is unknown

Run monochange step 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"

[changelog]
templates = [
	"#### {{ summary }}\n\n{{ details }}\n\n{{ context }}",
	"#### {{ summary }}\n\n{{ context }}",
	"#### {{ summary }}\n\n{{ details }}",
	"- {{ summary }}",
]

[package.sdk-core]
path = "crates/sdk_core"
[package.sdk-core.changelog.types]
security = { bump = "patch", section = "Security" }

[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",
]

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

[[cli.discover.inputs]]
name = "format"
type = "choice"
choices = ["text", "json", "json-min"]
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", "json-min"]
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", "json-min"]
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", "json-min"]
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", "json-min"]
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.