Skip to content

Releasing multiple packages

Sometimes you have more than one package in a repository, commonly called a monorepo. This tutorial will show you how to:

  1. Create a Knope config file
  2. Document changes that only impact some packages
  3. Document changes that impact all packages

Prerequisites

  • Git: The git CLI must be available in your terminal. It’s helpful if you know the basics of commits and tags.
  • A text editor: You’ll be editing Markdown and TOML files. Visual Studio Code is a good free choice.
  • Familiarity with a command line terminal, like “Terminal” on macOS or “PowerShell” Windows
  • A GitHub account (you can use an alternative, but the results will be different)
  • Install Knope

Create a new Git repo

Start by creating a new directory, moving into it, and initializing a Git repository.

Terminal window
mkdir knope-tutorial
cd knope-tutorial
git init

This repo will eventually need to be on GitHub so Knope can create releases for it. You can do that with the GitHub CLI:

Terminal window
gh repo create --private knope-tutorial --source .

Create a Knope config file

Knope requires a config file to support multiple packages. You could use knope --generate to create the default config, but it wouldn’t be quite right for multiple packages.

Instead, create this by hand:

knope.toml
[packages.pizza]
versioned_files = ["pizza/Cargo.toml"]
changelog = "pizza/CHANGELOG.md"
[packages.toppings]
versioned_files = ["toppings/Cargo.toml"]
changelog = "toppings/CHANGELOG.md"
[packages.calzone]
versioned_files = ["calzone/Cargo.toml"]
changelog = "calzone/CHANGELOG.md"
[github]
owner = ""
repo = "knope-tutorial"

Those referenced files must exist as well:

pizza/Cargo.toml
[package]
name = "pizza"
version = "3.14.15"
pizza/CHANGELOG.md
# Pizza Changelog
Documenting everything new in the world of Pizza.
toppings/Cargo.toml
[package]
name = "toppings"
version = "1.209.0"
toppings/CHANGELOG.md
# Toppings Changelog
New toppings are added here
calzone/Cargo.toml
[package]
name = "calzone"
version = "0.14.2"
calzone/CHANGELOG.md
# Calzone Changelog

Commit this to serve as a baseline for the rest of the tutorial:

Terminal window
git add .
git commit -m "Initial setup"

Documenting changes

As with a single package, you can document changes with either changesets or conventional commits.

Changesets

Terminal window
knope document-change

Knope will ask which packages the change impacts:

? Which packages does this change affect?
[x] pizza
[ ] toppings
> [x] calzone
[↑↓ to move, space to select one, → to all, ← to none, type to filter]

Use the arrow keys and space bar to select pizza and calzone, then press enter. Next, select patch as the change type for each:

> Which packages does this change affect? pizza, calzone
> What type of change is this for pizza? patch
? What type of change is this for calzone?
major
minor
> patch
[↑↓ to move, enter to select, type to filter]

Finally, summarize the change:

> Which packages does this change affect? pizza, calzone
> What type of change is this for pizza? patch
> What type of change is this for calzone? patch
? What is a short summary of this change? The cheese is now distributed more evenly
[This will be used as a header in the changelog]

This will create a change file that looks like this:

.changeset/the_cheese_is_now_distributed_more_evenly.md
---
pizza: patch
calzone: patch
---
# The cheese is now distributed more evenly

It includes the name of each package that the change impacts, the type of change for each of those packages, and the summary. At this point, you could add as much Markdown as you want to the bottom of the file to describe the change more fully.

Knope can show you a preview of the upcoming release:

Terminal window
knope release --dry-run
Would delete: .changeset/the_cheese_is_now_distributed_more_evenly.md
Would add the following to pizza/Cargo.toml: 3.14.16
Would add the following to pizza/CHANGELOG.md:
## 3.14.16 (2023-11-11)
### Fixes
- The cheese is now distributed more evenly
Would add files to git:
pizza/Cargo.toml
pizza/CHANGELOG.md
.changeset/the_cheese_is_now_distributed_more_evenly.md
Would add the following to calzone/Cargo.toml: 0.14.3
Would add the following to calzone/CHANGELOG.md:
## 0.14.3 (2023-11-11)
### Fixes
- The cheese is now distributed more evenly
Would add files to git:
calzone/Cargo.toml
calzone/CHANGELOG.md
.changeset/the_cheese_is_now_distributed_more_evenly.md
Would run git commit -m "chore: prepare release"
Would run git push
Would create a release on GitHub with name pizza 3.14.16 (2023-11-11) and tag pizza/v3.14.16 and body:
## Fixes
- The cheese is now distributed more evenly
Would create a release on GitHub with name calzone 0.14.3 (2023-11-11) and tag calzone/v0.14.3 and body:
## Fixes
- The cheese is now distributed more evenly

Knope updates the versions and changelogs of each package independently. Because there are no changes to toppings, nothing will happen to it.

Conventional commits

Changesets work great for monorepos by default, but conventional commits require a bit more care. A basic conventional commit will apply to all packages:

Terminal window
rm .changeset/the_cheese_is_now_distributed_more_evenly.md # Revert changeset
git commit --allow-empty -m "fix: The cheese is now distributed more evenly" # create basic conventional commit
knope release --dry-run # See what Knope would do with it
Would add the following to pizza/Cargo.toml: 3.14.16
Would add the following to pizza/CHANGELOG.md:
## 3.14.16 (2023-11-11)
### Fixes
- The cheese is now distributed more evenly
Would add files to git:
pizza/Cargo.toml
pizza/CHANGELOG.md
Would add the following to toppings/Cargo.toml: 1.209.1
Would add the following to toppings/CHANGELOG.md:
## 1.209.1 (2023-11-11)
### Fixes
- The cheese is now distributed more evenly
Would add files to git:
toppings/Cargo.toml
toppings/CHANGELOG.md
Would add the following to calzone/Cargo.toml: 0.14.3
Would add the following to calzone/CHANGELOG.md:
## 0.14.3 (2023-11-11)
### Fixes
- The cheese is now distributed more evenly
Would add files to git:
calzone/Cargo.toml
calzone/CHANGELOG.md
Would run git commit -m "chore: prepare release"
Would run git push
Would create a release on GitHub with name pizza 3.14.16 (2023-11-11) and tag pizza/v3.14.16 and body:
## Fixes
- The cheese is now distributed more evenly
Would create a release on GitHub with name toppings 1.209.1 (2023-11-11) and tag toppings/v1.209.1 and body:
## Fixes
- The cheese is now distributed more evenly
Would create a release on GitHub with name calzone 0.14.3 (2023-11-11) and tag calzone/v0.14.3 and body:
## Fixes
- The cheese is now distributed more evenly

You can limit commits to specific packages using scopes:

knope.toml
[packages.pizza]
versioned_files = ["pizza/Cargo.toml"]
changelog = "pizza/CHANGELOG.md"
scopes = ["pizza"]
[packages.toppings]
versioned_files = ["toppings/Cargo.toml"]
changelog = "toppings/CHANGELOG.md"
[packages.calzone]
versioned_files = ["calzone/Cargo.toml"]
changelog = "calzone/CHANGELOG.md"
scopes = ["calzone"]

Now, conventional commits which have the pizza scope will only affect the pizza package, and calzone commits will only affect the calzone package. Commits without scopes (like the one you just created) will still affect all packages, you can verify that with knope release --dry-run again.

It’s possible to recreate the changeset results using two commits:

But the point of conventional commits is to document the changes made within the commit, so one change should be in one commit, not two. It’s better to have a new scope that impacts both packages:

knope.toml
[packages.pizza]
versioned_files = ["pizza/Cargo.toml"]
changelog = "pizza/CHANGELOG.md"
scopes = ["pizza", "pizza-and-calzone"]
[packages.toppings]
versioned_files = ["toppings/Cargo.toml"]
changelog = "toppings/CHANGELOG.md"
[packages.calzone]
versioned_files = ["calzone/Cargo.toml"]
changelog = "calzone/CHANGELOG.md"
scopes = ["calzone", "pizza-and-calzone"]

Now a single commit can impact both pizza and calzone, without impacting toppings:

Terminal window
git reset HEAD~ # undo the unscoped commit
git commit --allow-empty -m "fix(pizza-and-calzone): The cheese is now distributed more evenly"
Terminal window
knope release --dry-run
Would add the following to pizza/Cargo.toml: 3.14.16
Would add the following to pizza/CHANGELOG.md:
## 3.14.16 (2023-11-11)
### Fixes
- The cheese is now distributed more evenly
Would add files to git:
pizza/Cargo.toml
pizza/CHANGELOG.md
Would add the following to calzone/Cargo.toml: 0.14.3
Would add the following to calzone/CHANGELOG.md:
## 0.14.3 (2023-11-11)
### Fixes
- The cheese is now distributed more evenly
Would add files to git:
calzone/Cargo.toml
calzone/CHANGELOG.md
Would run git commit -m "chore: prepare release"
Would run git push
Would create a release on GitHub with name pizza 3.14.16 (2023-11-11) and tag pizza/v3.14.16 and body:
## Fixes
- The cheese is now distributed more evenly
Would create a release on GitHub with name calzone 0.14.3 (2023-11-11) and tag calzone/v0.14.3 and body:
## Fixes
- The cheese is now distributed more evenly

This is the same result as the changeset, but with a single commit message instead.

Time to try it out for real:

Terminal window
knope release

If you don’t have a GitHub token set, Knope will prompt you to create one and paste it into the terminal. Once you do, you’ll see some output from Git. If there are no errors, your releases should exist!

Open your repo in GitHub (you can use gh repo view --web), and click on “Releases” on the side. You should see something like this:

Knope creates one release on GitHub per package so that it’s easy for consumers to see what’s changed in only the packages they care about!

Wrapping up

In this tutorial, you:

  1. Configured Knope for a monorepo
  2. Documented changes using both changesets and conventional commits
  3. Created GitHub releases for each package