From 8c75446314d9ce003895ec1e82466e780ea5a5db Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Mon, 19 Jun 2023 08:49:19 +0200 Subject: [PATCH 01/17] initial release docs structure --- CONTRIBUTING/RELEASING.md | 117 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 CONTRIBUTING/RELEASING.md diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md new file mode 100644 index 000000000000..777ca6a6fea1 --- /dev/null +++ b/CONTRIBUTING/RELEASING.md @@ -0,0 +1,117 @@ +# Releasing + +> **Note** +> This document is only really relevant for any of the core team members that actually have permissions to release new versions of Storybook. Feel free to read it out of interest or to suggest changes, but as a regular contributor or maintainer you don't have to care about this. + +## Table of Contents + +- [Introduction](#introduction) +- [How To Release](#how-to-release) + - [Prereleases](#prereleases) + - [Patch releases](#patch-releases) + - [Manual Changes](#manual-changes) +- [Releasing Locally in Case of Emergency 🚨](#releasing-locally-in-case-of-emergency-) +- [Versioning Scenarios](#versioning-scenarios) + - [Prereleases - `7.1.0-alpha.12` -\> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13) + - [Prerelease promotions - `7.1.0-alpha.13` -\> `7.1.0-beta.0`](#prerelease-promotions---710-alpha13---710-beta0) + - [Minor/major releases - `7.1.0-rc.2` -\> `7.1.0` or `8.0.0-rc.3` -\> `8.0.0`](#minormajor-releases---710-rc2---710-or-800-rc3---800) + - [Patch releases to stable - subset of `7.1.0-alpha.13` -\> `7.0.14`](#patch-releases-to-stable---subset-of-710-alpha13---7014) + - [Patch releases to earlier versions - subset of `7.1.0-alpha.13` -\> `6.5.14`](#patch-releases-to-earlier-versions---subset-of-710-alpha13---6514) +- [FAQ](#faq) + - [How do I make changes to the release scripts?](#how-do-i-make-changes-to-the-release-scripts) + - [Why do I need to re-trigger workflows to update the changelog?](#why-do-i-need-to-re-trigger-workflows-to-update-the-changelog) + - [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared) + +## Introduction + +- what this document describes +- +- branches + +## How To Release + +### Prereleases + +> **Note** +> This actually also covers how to promote a prerelease to stable. This is basically any other releases than backported patch releases + +### Patch releases + +All changes should already have been prereleased + +### Manual Changes + +## Releasing Locally in Case of Emergency 🚨 + +## Versioning Scenarios + +There are five types of releases that will be handled somewhat differently, but following the overall same principles as described above. + +### Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13` + +These happen multiple times a week + +1. A PR will automatically be opened from a fresh branch `version-from-7.1.0-alpha.16` to `next-release` on every push to `next`. If the PR is already open, it will be kept up-to-date with description changes and force pushing commits. This process can also be manually triggered if needed. +2. The PR will consist of: + 1. Version bumps in all `package.json`s and other files like `versions.ts` + 2. Changes to `CHANGELOG.md` generated as specced below + 3. Changes listed in the PR description, along with a checklist to go through manually +3. When we're ready to release, a Releaser will go through the check list: + 1. Freeze the PR by applying the "freeze" label on it, stopping any actions from modifying it further + 2. QA each PR that is part of the release: + 1. Is the changelog high quality - it's based on PR titles, which are usually bad. + 2. Change any PR titles necessary + 3. Check that each PR content is high quality, has it been tested, are we sure it's not a breaking change, etc. + 4. revert any bad PRs + 3. If necessary, manually trigger the workflow again, to reflect changes to PR titles and reverts (manual triggers should ignore the "freeze" label) + 4. Merge the PR to `next-release` +4. When the PR is merged, an action will: + 1. publish all packages + 2. create a GitHub Release + 3. tag the commit + 4. merge `next-release` back to `next`. If this causes a merge conflict, this will have to be done manually +5. ... the cycle starts over + +### Prerelease promotions - `7.1.0-alpha.13` -> `7.1.0-beta.0` + +These happen once every 1-2 months + +Same process as above, except before merging, the Releaser manually triggers the Action with a "tag: beta" input, that will change versions from the proposed `7.1.0-alpha.14` to `7.1.0-beta.0`. + +### Minor/major releases - `7.1.0-rc.2` -> `7.1.0` or `8.0.0-rc.3` -> `8.0.0` + +These happen once every quarter + +Same process as above, except before merging, the Releaser manually triggers the Action with a "tag: stable" input, that will change versions from the proposed `7.1.0-rc.3` to `7.1.0`. When the PR is merged, the action will do the usual publishing work, and **force merge `next` into `main`**. The following GitHub Action that triggers on a push to `next` will generate a release PR with `7.2.0-alpha.0`, to start the cycle over. + +### Patch releases to stable - subset of `7.1.0-alpha.13` -> `7.0.14` + +These happen roughly every second week + +This process is a bit different from the above because it needs to merge to the `main` branch and not `next`, but the principle is the same. + +1. Any PR to `next` that needs to be patched back to stable, needs to have a "patch" label +2. On pushes to `next`, an action check for any such PRs with the "patch" label +3. It will create a release branch (`version-from-7.0.11`) and PR similar to the one for prereleases, except that it targets `main`. +4. Each "patch" PR that it finds it will attempt to cherry-pick to the `version-from-7.0.11` branch +5. Sometimes it might cause merge conflicts, in which case the PR will be skipped +6. When all is done, the description for the release PR will contain a list of PRs that couldn't be cherry picked, for the Releaser to manually do that and solve any merge conflicts. +7. An important additional step for the Releaser is to check that all PRs are actually patches/fixes, and not new features, and that everything actually works, given that some cherry picked PRs could rely on functionality not yet found in stable. + +When the PR has been merged to `main-release` by the Releaser, after the usual publishing steps, the action will also label all patched PRs with "picked" so they are ignored for the next patch release. + +### Patch releases to earlier versions - subset of `7.1.0-alpha.13` -> `6.5.14` + +These happen 2-3 times a year + +Given that this happens so rarely on a case by case basis, I'm okay with this being a completely manual process. The only thing we then need to keep in mind, is that all these versioning and publishing scripts needs to be executable locally, outside of a GitHub Action. + +## FAQ + +### How do I make changes to the release scripts? + +(patch script changes back to main, either manually or via the patching flow) + +### Why do I need to re-trigger workflows to update the changelog? + +### Why are no release PRs being prepared? From 7a3cf7563a42301ffccf2440dea7a4c93d4f8dd5 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 20 Jun 2023 08:52:30 +0200 Subject: [PATCH 02/17] added introduction, diagrams --- CONTRIBUTING/RELEASING.md | 140 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 777ca6a6fea1..5b102eac216e 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -6,9 +6,14 @@ ## Table of Contents - [Introduction](#introduction) -- [How To Release](#how-to-release) + - [Branches](#branches) +- [The Release Pull Requests](#the-release-pull-requests) - [Prereleases](#prereleases) - [Patch releases](#patch-releases) + - [Publishing](#publishing) +- [How To Release](#how-to-release) + - [Prereleases](#prereleases-1) + - [Patch releases](#patch-releases-1) - [Manual Changes](#manual-changes) - [Releasing Locally in Case of Emergency 🚨](#releasing-locally-in-case-of-emergency-) - [Versioning Scenarios](#versioning-scenarios) @@ -17,23 +22,144 @@ - [Minor/major releases - `7.1.0-rc.2` -\> `7.1.0` or `8.0.0-rc.3` -\> `8.0.0`](#minormajor-releases---710-rc2---710-or-800-rc3---800) - [Patch releases to stable - subset of `7.1.0-alpha.13` -\> `7.0.14`](#patch-releases-to-stable---subset-of-710-alpha13---7014) - [Patch releases to earlier versions - subset of `7.1.0-alpha.13` -\> `6.5.14`](#patch-releases-to-earlier-versions---subset-of-710-alpha13---6514) + - [Prerelease of upcoming patch release - `7.0.20` -\> `7.0.21-alpha.0`](#prerelease-of-upcoming-patch-release---7020---7021-alpha0) - [FAQ](#faq) - [How do I make changes to the release scripts?](#how-do-i-make-changes-to-the-release-scripts) - [Why do I need to re-trigger workflows to update the changelog?](#why-do-i-need-to-re-trigger-workflows-to-update-the-changelog) - [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared) + - [Why do we need separate release branches?](#why-do-we-need-separate-release-branches) ## Introduction -- what this document describes -- -- branches +This document describes how the release process for the Storybook monorepo is set up. There are mainly two different types of releases: + +1. Prereleases and major/minor releases - releasing any content that is on the `next` branch +2. Patch releases - picking any content from `next` to `main`, that needs to be patched back to the current stable minor release + +The release process is based on automatically created "Release Pull Requests", that when merged will trigger a new version to be released. A designated Releaser - can be a different team member from time to time - will go through the release process in the current Release PR. + +The process is implemented with a set of NodeJS scripts at [`scripts/release`](../scripts/release/), invoked by three GitHub Actions workflows: + +- [Prepare Prerelease PR](../.github/workflows/prepare-prerelease.yml) +- [Prepare Patch PR](../.github/workflows/prepare-patch-release.yml) +- [Publish](../.github/workflows/publish.yml) + +### Branches + +To understand how all of this fits together in the repository, it's important to understand the branching strategy used. + +All development is done against the default `next` branch, and any new features/bugfixes will almost always target that branch. The `next` branch contains the content ready to be released in the next prerelease. Any upcoming prerelease (eg. `v7.1.0-alpha.22`) will release the content of `next`. + +The `main` branch contains the content for the current stable release, eg. `v7.0.20`. + +Sometimes we're making changes that both needs to be in the next major/minor release, and in the current patch release. That might be bugfixes or small quality-of-life improvements. Making all changes target `next` ensures that the bugfix will land in the upcoming prerelease. To also get the change patched back to the current minor version (eg. from `7.1.0-alpha.20` to `7.0.18`), the PR containing the fix will get the **"patch"** label. That label tells the release workflow that it should pick that PR for the next patch release. +This structure ensures that the changes are safely tried out in a prerelease, before being released to stable. + +There are many nuances to the process defined above, which are described in greater detail in [the "Versioning Scenarios" section](#versioning-scenarios) below. + +The actual (pre)releases aren't actually released from `next` nor `main`, but from `next-release` and `latest-release` respectively. That means that `next-release` and `latest-release` follow `next` and `main` closely, but they are not always in complete sync - which is on purpose. The reason for this indirection is described in [the "Why do we need separate release branches?" section](#why-do-we-need-separate-release-branches) below. + +At a high level, the branches in the monorepo can be described in this diagram (greatly simplified): + +```mermaid +%%{init: { 'gitGraph': { 'showCommitLabel': false } } }%% +gitGraph + commit + branch latest-release + branch next + checkout next + commit + branch next-release + checkout next-release + commit + commit tag: "7.1.0-alpha.18" + checkout next + merge next-release + commit id: "bugfix" + commit + checkout latest-release + cherry-pick id: "bugfix" + commit tag: "7.0.20" + checkout next-release + merge next + commit tag: "7.1.0-alpha.19" + checkout next + merge next-release + commit + checkout main + merge latest-release +``` + +```mermaid +gitGraph + commit + branch latest-release + branch next + checkout next + commit + branch next-release + branch new-feature + checkout new-feature + commit + commit + checkout next + merge new-feature + commit + branch some-patched-bugfix + checkout some-patched-bugfix + commit + commit id: "patched-bugfix" + checkout next + merge some-patched-bugfix + commit + branch version-from-7.1.0-alpha.20 + checkout version-from-7.1.0-alpha.20 + commit + checkout next-release + merge version-from-7.1.0-alpha.20 tag: "7.1.0-alpha.21" + checkout next + merge next-release + checkout main + branch version-from-7.0.18 + commit + cherry-pick id: "patched-bugfix" + checkout latest-release + merge version-from-7.0.18 tag: "7.0.19" + checkout main + merge latest-release +``` + +## The Release Pull Requests + +The release pull requests are automatically created by two different GitHub Actions workflows, one for each type of release. These pull requests are the "interface" for the Releaser to create a new release. The behavior between the two is very similar, with some minor differences described in the subsections. The high-level flow is: + +1. When a PR is merged to `next` (or a commit is pushed), both release pull requests are (re)generated +2. They create a new branch - `version-(patch|prerelease)-from-` +3. Bump all versions according to the version strategy (more on that below) +4. Update `CHANGELOG(.prerelease).md` with all changes detected +5. Commit everything +6. **Force push** +7. Open/edit pull request towards `next-release` or `latest-release` + +A few important things to note in this flow: + +- The PRs are regenerated on any changes to `next`, and a re-generation can be manually triggered as well (more on that in [How To Release](#how-to-release)) +- The changes are force pushed to the branch. Combining that with the bullet above, it means that if you commit any manual changes on the release branch before merging it, they risk being overwritten if a new change is merged to `next`, triggering the workflow. To mitigate this, [apply the **"freeze"** label to the pull request](#how-to-release). +- The version bumps and changelogs are committed during the preparation, but the packages are _not actually published_ until later. This is a non-standard paradigm, where usually bumping versions and publishing packages happens at the same time. +- The release pull requests don't target their working branches (`next` and `main`), but rather the release-focused `next-release` and `latest-release`. + +### Prereleases + +### Patch releases + +### Publishing ## How To Release ### Prereleases > **Note** -> This actually also covers how to promote a prerelease to stable. This is basically any other releases than backported patch releases +> This actually also covers how to promote a prerelease to stable. This is basically any other releases than backported patch releases, which are described in the next section ### Patch releases @@ -106,6 +232,8 @@ These happen 2-3 times a year Given that this happens so rarely on a case by case basis, I'm okay with this being a completely manual process. The only thing we then need to keep in mind, is that all these versioning and publishing scripts needs to be executable locally, outside of a GitHub Action. +### Prerelease of upcoming patch release - `7.0.20` -> `7.0.21-alpha.0` + ## FAQ ### How do I make changes to the release scripts? @@ -115,3 +243,5 @@ Given that this happens so rarely on a case by case basis, I'm okay with this be ### Why do I need to re-trigger workflows to update the changelog? ### Why are no release PRs being prepared? + +### Why do we need separate release branches? From 1721ac69e653638f21286d88855d435528a10ef3 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Wed, 21 Jun 2023 10:23:41 +0200 Subject: [PATCH 03/17] more words. some good, some bad --- CONTRIBUTING/RELEASING.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 5b102eac216e..8c79f52ecfc0 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -16,6 +16,7 @@ - [Patch releases](#patch-releases-1) - [Manual Changes](#manual-changes) - [Releasing Locally in Case of Emergency 🚨](#releasing-locally-in-case-of-emergency-) +- [Canary Releases](#canary-releases) - [Versioning Scenarios](#versioning-scenarios) - [Prereleases - `7.1.0-alpha.12` -\> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13) - [Prerelease promotions - `7.1.0-alpha.13` -\> `7.1.0-beta.0`](#prerelease-promotions---710-alpha13---710-beta0) @@ -26,6 +27,8 @@ - [FAQ](#faq) - [How do I make changes to the release scripts?](#how-do-i-make-changes-to-the-release-scripts) - [Why do I need to re-trigger workflows to update the changelog?](#why-do-i-need-to-re-trigger-workflows-to-update-the-changelog) + - [Which combination of inputs creates the version bump I need?](#which-combination-of-inputs-creates-the-version-bump-i-need) + - [Which changes are considered "releasable", and what does it mean?](#which-changes-are-considered-releasable-and-what-does-it-mean) - [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared) - [Why do we need separate release branches?](#why-do-we-need-separate-release-branches) @@ -144,12 +147,18 @@ The release pull requests are automatically created by two different GitHub Acti A few important things to note in this flow: - The PRs are regenerated on any changes to `next`, and a re-generation can be manually triggered as well (more on that in [How To Release](#how-to-release)) -- The changes are force pushed to the branch. Combining that with the bullet above, it means that if you commit any manual changes on the release branch before merging it, they risk being overwritten if a new change is merged to `next`, triggering the workflow. To mitigate this, [apply the **"freeze"** label to the pull request](#how-to-release). -- The version bumps and changelogs are committed during the preparation, but the packages are _not actually published_ until later. This is a non-standard paradigm, where usually bumping versions and publishing packages happens at the same time. +- The changes are force pushed to the branch. Combining that with the bullet above, it means that if you commit any manual changes on the release branch before merging it, they risk being overwritten if a new change is merged to `next` by someone else, triggering the workflow. To mitigate this, [apply the **"freeze"** label to the pull request](#how-to-release). +- The version bumps and changelogs are committed during the preparation, but the packages are _not actually published_ until [later](#publishing). This is a non-standard paradigm, where usually bumping versions and publishing packages happens at the same time. - The release pull requests don't target their working branches (`next` and `main`), but rather the release-focused `next-release` and `latest-release`. ### Prereleases +Prereleases are prepared with all content from next `next`. The changelog is generated by using the git history, and looking up all the commits and PRs between the currently released prerelease (on `next-release`) and the yet-to-be-released version. + +The default versioning strategy is to bump the current prerelease number, as described in [Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13). If there is not prerelease number (we just released a new stable minor/major) it will add one to a patch bump, so it would go from `7.2.0` to `7.2.1-0`. + +Prerelease PRs are only prepared if there are actual changes to release, otherwise the workflow will be cancelled. `next` can have new content which is only labeled with "build" or "documentation", which isn't user-facing so it's [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean). In that case it doesn't make sense to create a release, as it won't bump versions nor write changelogs so it would just merge the same content back to `next`. This is explained more deeply in [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared). + ### Patch releases ### Publishing @@ -169,6 +178,10 @@ All changes should already have been prereleased ## Releasing Locally in Case of Emergency 🚨 +## Canary Releases + +Not implemented yet. + ## Versioning Scenarios There are five types of releases that will be handled somewhat differently, but following the overall same principles as described above. @@ -242,6 +255,14 @@ Given that this happens so rarely on a case by case basis, I'm okay with this be ### Why do I need to re-trigger workflows to update the changelog? +### Which combination of inputs creates the version bump I need? + +Link to tests, and to version strategies above. + +### Which changes are considered "releasable", and what does it mean? + +link to the enums + ### Why are no release PRs being prepared? ### Why do we need separate release branches? From 2a4d7822a9c83626748f8f6903e98b8f308ccc61 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Wed, 21 Jun 2023 12:51:08 +0200 Subject: [PATCH 04/17] describe patch preparation --- CONTRIBUTING/RELEASING.md | 101 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 8c79f52ecfc0..fc0a25df2e31 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -14,7 +14,7 @@ - [How To Release](#how-to-release) - [Prereleases](#prereleases-1) - [Patch releases](#patch-releases-1) - - [Manual Changes](#manual-changes) + - [Making Manual Changes](#making-manual-changes) - [Releasing Locally in Case of Emergency 🚨](#releasing-locally-in-case-of-emergency-) - [Canary Releases](#canary-releases) - [Versioning Scenarios](#versioning-scenarios) @@ -153,16 +153,109 @@ A few important things to note in this flow: ### Prereleases -Prereleases are prepared with all content from next `next`. The changelog is generated by using the git history, and looking up all the commits and PRs between the currently released prerelease (on `next-release`) and the yet-to-be-released version. +> **Note** +> Workflow: [`prepare-prerelease.yml`](../.github/workflows/prepare-prerelease.yml) + +Prereleases are prepared with all content from `next`. The changelog is generated by using the git history, and looking up all the commits and PRs between the currently released prerelease (on `next-release`) and `HEAD` of `next`. -The default versioning strategy is to bump the current prerelease number, as described in [Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13). If there is not prerelease number (we just released a new stable minor/major) it will add one to a patch bump, so it would go from `7.2.0` to `7.2.1-0`. +The default versioning strategy is to bump the current prerelease number, as described in [Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13). If there is no prerelease number (ie. we just released a new stable minor/major) it will add one to a patch bump, so it would go from `7.2.0` to `7.2.1-0` per default. Prerelease PRs are only prepared if there are actual changes to release, otherwise the workflow will be cancelled. `next` can have new content which is only labeled with "build" or "documentation", which isn't user-facing so it's [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean). In that case it doesn't make sense to create a release, as it won't bump versions nor write changelogs so it would just merge the same content back to `next`. This is explained more deeply in [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared). +The preparation workflow will create a new branch from `next` called `version-from-`, and open a pull request that targets `next-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `next-release` into `next`. + +Here's an example of a workflow where a feature and a bugfix have been created and then released to the a new `7.1.0-alpha.29` version. All the highlighted commits (square dots) are the ones that will be considered when generating the changelog. + +```mermaid +%%{init: { 'gitGraph': { 'mainBranchName': 'next' } } }%% +gitGraph + commit + branch next-release + checkout next-release + commit tag: "7.1.0-alpha.28" + checkout next + merge next-release + commit type: HIGHLIGHT id: "direct commit" + branch new-feature + checkout new-feature + commit + commit + checkout next + merge new-feature type: HIGHLIGHT + branch some-bugfix + checkout some-bugfix + commit + checkout next + merge some-bugfix type: HIGHLIGHT + branch version-from-7.1.0-alpha.28 + checkout version-from-7.1.0-alpha.28 + commit id: "bump version" + checkout next-release + merge version-from-7.1.0-alpha.28 tag: "7.1.0-alpha.29" + checkout next + merge next-release +``` + ### Patch releases +> **Note** +> Workflow: [`prepare-patch-release.yml`](../.github/workflows/prepare-patch-release.yml) + +Patch releases are prepared by [cherry picking](https://www.atlassian.com/git/tutorials/cherry-pick) any merged, unreleased pull requests to `next` that have the "**patch**" label applied. The merge commit of said pull requests are cherry picked. + +Sometimes it's desired to pick pull requests back to `main` even if they are [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean), so opposite of the prerelease preparation, patch releases will _not cancel_ if the content os not releasable. +On the surface it might not make sense to create a new patch release if the changes are only for documentation and/or internal build systems. But getting the changes back to `main` is the only way to get the documentation deployed to the production docs site. You also might want to cherry pick changes to internal CI to fix issues or similar. Both of these cases are valid scenarios where you want to cherry pick the changes in without being blocked on "releasable" content to be ready. In these cases where all cherry picks are non-releasable, the preparation workflow creates a "merging" pull request instead of a "releasing" pull request. In this state it doesn't bump versions and it doesn't update changelogs, it just cherry picks the changes and allows you to merge them into `main-release` -> `main`. + +The preparation workflow sequentially cherry picks each patch pull request to it's branch. Sometimes this cherry picking fails because of conflicts or for other reasons, in which case it ignores it and moves on to the next. All the failing cherry picks are finally listed in the release pull request's description, for the Releaser to manually cherry pick during the release process. +This problem occurs more often the more `main` and `next` diverges, ie. the longer it has been since a stable major/minor release. + +Similar to the prerelease flow, the preparation workflow for patches will create a new branch from `main` called `version-from-`, and open a pull request that targets `main-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `main-release` into `main`. + +Here's an example of a workflow where a feature and two bugfixes have been merged to `next`. Only the bugfixes have the "**patch**" label, so only those two goes into the new `7.0.19` release. Note that while the diagram shows the commits _on_ the bugfix branches being cherry-picked, it's actually their merge commits to `next` that gets picked - this is a limitation of mermaid graphs. + +```mermaid +gitGraph + commit + branch latest-release + branch next + checkout latest-release + commit tag: "v7.0.18" + checkout main + merge latest-release + checkout next + commit + branch some-patched-bugfix + checkout some-patched-bugfix + commit + commit id: "patch1" + checkout next + merge some-patched-bugfix + branch new-feature + checkout new-feature + commit + checkout next + merge new-feature + branch other-patched-bugfix + checkout other-patched-bugfix + commit id: "patch2" + checkout next + merge other-patched-bugfix + checkout main + branch version-from-7.0.18 + cherry-pick id: "patch1" + cherry-pick id: "patch2" + commit id: "version bump" + checkout latest-release + merge version-from-7.0.18 tag: "v7.0.19" + checkout main + merge latest-release +``` + ### Publishing +> **Note** +> Workflow: [`publish.yml`](../.github/workflows/publish.yml) + ## How To Release ### Prereleases @@ -174,7 +267,7 @@ Prerelease PRs are only prepared if there are actual changes to release, otherwi All changes should already have been prereleased -### Manual Changes +### Making Manual Changes ## Releasing Locally in Case of Emergency 🚨 From c1925625bd37f7393243aee54b1e5cc04be63fde Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Wed, 21 Jun 2023 14:06:23 +0200 Subject: [PATCH 05/17] even more words now --- CONTRIBUTING/RELEASING.md | 52 +++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index fc0a25df2e31..b7a4b33b3ff9 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -11,7 +11,7 @@ - [Prereleases](#prereleases) - [Patch releases](#patch-releases) - [Publishing](#publishing) -- [How To Release](#how-to-release) +- [👉 How To Release](#-how-to-release) - [Prereleases](#prereleases-1) - [Patch releases](#patch-releases-1) - [Making Manual Changes](#making-manual-changes) @@ -47,15 +47,18 @@ The process is implemented with a set of NodeJS scripts at [`scripts/release`](. - [Prepare Patch PR](../.github/workflows/prepare-patch-release.yml) - [Publish](../.github/workflows/publish.yml) +> **Note** +> Throughout this document the distinction between **patch** release and **prereleases** are made. This is a simplification, since stable major and minor releases work the exact same way as **prereleases**, so that term covers those two cases as well. The distinction reflects the difference between releases updating the existing minor version on `main` with a new patch, or releasing a new minor/major/prerelease from `next`. + ### Branches To understand how all of this fits together in the repository, it's important to understand the branching strategy used. -All development is done against the default `next` branch, and any new features/bugfixes will almost always target that branch. The `next` branch contains the content ready to be released in the next prerelease. Any upcoming prerelease (eg. `v7.1.0-alpha.22`) will release the content of `next`. +All development is done against the default `next` branch, and any new features/bug fixes will almost always target that branch. The `next` branch contains the content ready to be released in the next prerelease. Any upcoming prerelease (eg. `v7.1.0-alpha.22`) will release the content of `next`. The `main` branch contains the content for the current stable release, eg. `v7.0.20`. -Sometimes we're making changes that both needs to be in the next major/minor release, and in the current patch release. That might be bugfixes or small quality-of-life improvements. Making all changes target `next` ensures that the bugfix will land in the upcoming prerelease. To also get the change patched back to the current minor version (eg. from `7.1.0-alpha.20` to `7.0.18`), the PR containing the fix will get the **"patch"** label. That label tells the release workflow that it should pick that PR for the next patch release. +Sometimes we're making changes that both need to be in the next major/minor release, and in the current patch release. That might be bug fixes or small quality-of-life improvements. Making all changes target `next` ensures that the bugfix will land in the upcoming prerelease. To also get the change patched back to the current minor version (eg. from `7.1.0-alpha.20` to `7.0.18`), the PR containing the fix will get the **"patch"** label. That label tells the release workflow that it should pick that PR for the next patch release. This structure ensures that the changes are safely tried out in a prerelease, before being released to stable. There are many nuances to the process defined above, which are described in greater detail in [the "Versioning Scenarios" section](#versioning-scenarios) below. @@ -160,11 +163,11 @@ Prereleases are prepared with all content from `next`. The changelog is generate The default versioning strategy is to bump the current prerelease number, as described in [Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13). If there is no prerelease number (ie. we just released a new stable minor/major) it will add one to a patch bump, so it would go from `7.2.0` to `7.2.1-0` per default. -Prerelease PRs are only prepared if there are actual changes to release, otherwise the workflow will be cancelled. `next` can have new content which is only labeled with "build" or "documentation", which isn't user-facing so it's [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean). In that case it doesn't make sense to create a release, as it won't bump versions nor write changelogs so it would just merge the same content back to `next`. This is explained more deeply in [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared). +Prerelease PRs are only prepared if there are actual changes to release, otherwise, the workflow will be canceled. `next` can have new content which is only labeled with "build" or "documentation", which isn't user-facing so it's [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean). In that case, it doesn't make sense to create a release, as it won't bump versions nor write changelogs so it would just merge the same content back to `next`. This is explained more deeply in [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared). The preparation workflow will create a new branch from `next` called `version-from-`, and open a pull request that targets `next-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `next-release` into `next`. -Here's an example of a workflow where a feature and a bugfix have been created and then released to the a new `7.1.0-alpha.29` version. All the highlighted commits (square dots) are the ones that will be considered when generating the changelog. +Here's an example of a workflow where a feature and a bugfix have been created and then released to a new `7.1.0-alpha.29` version. All the highlighted commits (square dots) are the ones that will be considered when generating the changelog. ```mermaid %%{init: { 'gitGraph': { 'mainBranchName': 'next' } } }%% @@ -201,17 +204,17 @@ gitGraph > **Note** > Workflow: [`prepare-patch-release.yml`](../.github/workflows/prepare-patch-release.yml) -Patch releases are prepared by [cherry picking](https://www.atlassian.com/git/tutorials/cherry-pick) any merged, unreleased pull requests to `next` that have the "**patch**" label applied. The merge commit of said pull requests are cherry picked. +Patch releases are prepared by [cherry-picking](https://www.atlassian.com/git/tutorials/cherry-pick) any merged, unreleased pull requests to `next` that have the "**patch**" label applied. The merge commit of said pull requests are cherry-picked. -Sometimes it's desired to pick pull requests back to `main` even if they are [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean), so opposite of the prerelease preparation, patch releases will _not cancel_ if the content os not releasable. -On the surface it might not make sense to create a new patch release if the changes are only for documentation and/or internal build systems. But getting the changes back to `main` is the only way to get the documentation deployed to the production docs site. You also might want to cherry pick changes to internal CI to fix issues or similar. Both of these cases are valid scenarios where you want to cherry pick the changes in without being blocked on "releasable" content to be ready. In these cases where all cherry picks are non-releasable, the preparation workflow creates a "merging" pull request instead of a "releasing" pull request. In this state it doesn't bump versions and it doesn't update changelogs, it just cherry picks the changes and allows you to merge them into `main-release` -> `main`. +Sometimes it's desired to pick pull requests back to `main` even if they are [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean), so opposite of the prerelease preparation, patch releases will _not cancel_ if the content is not releasable. +On the surface, it might not make sense to create a new patch release if the changes are only for documentation and/or internal build systems. But getting the changes back to `main` is the only way to get the documentation deployed to the production docs site. You also might want to cherry-pick changes to internal CI to fix issues or similar. Both of these cases are valid scenarios where you want to cherry-pick the changes without being blocked on "releasable" content to be ready. In these cases where all cherry picks are non-releasable, the preparation workflow creates a "merging" pull request instead of a "releasing" pull request. In this state, it doesn't bump versions and it doesn't update changelogs, it just cherry-picks the changes and allows you to merge them into `latest-release` -> `main`. -The preparation workflow sequentially cherry picks each patch pull request to it's branch. Sometimes this cherry picking fails because of conflicts or for other reasons, in which case it ignores it and moves on to the next. All the failing cherry picks are finally listed in the release pull request's description, for the Releaser to manually cherry pick during the release process. +The preparation workflow sequentially cherry-picks each patch pull request to its branch. Sometimes this cherry-picking fails because of conflicts or for other reasons, in which case it ignores it and moves on to the next. All the failing cherry-picks are finally listed in the release pull request's description, for the Releaser to manually cherry-pick during the release process. This problem occurs more often the more `main` and `next` diverges, ie. the longer it has been since a stable major/minor release. -Similar to the prerelease flow, the preparation workflow for patches will create a new branch from `main` called `version-from-`, and open a pull request that targets `main-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `main-release` into `main`. +Similar to the prerelease flow, the preparation workflow for patches will create a new branch from `main` called `version-from-`, and open a pull request that targets `latest-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `latest-release` into `main`. -Here's an example of a workflow where a feature and two bugfixes have been merged to `next`. Only the bugfixes have the "**patch**" label, so only those two goes into the new `7.0.19` release. Note that while the diagram shows the commits _on_ the bugfix branches being cherry-picked, it's actually their merge commits to `next` that gets picked - this is a limitation of mermaid graphs. +Here's an example of a workflow where a feature and two bug fixes have been merged to `next`. Only the bug fixes have the "**patch**" label, so only those two go into the new `7.0.19` release. Note that while the diagram shows the commits _on_ the bugfix branches being cherry-picked, it's actually their merge commits to `next` that gets picked - this is a limitation of mermaid graphs. ```mermaid gitGraph @@ -256,12 +259,29 @@ gitGraph > **Note** > Workflow: [`publish.yml`](../.github/workflows/publish.yml) -## How To Release +When either a prerelease or a patch release branch is merged to `main|next-release`, the publishing workflow is triggered. This workflow does the following on a high level: -### Prereleases +1. Install dependencies and build all packages +2. Publish packages to npm +3. (If this is a patch release, add the "**picked**" label to all picked pull requests) +4. Create a new GitHub Release - also creating a version tag at the release branch (`latest-release` or `next-release`) +5. Merge the release branch into the core branch (`main` or `next`) +6. (If this is a patch release, copy the `CHANGELOG.md` changes from `main` to `next`) +7. (If this is [a promotion from a prerelease to a stable release](#minormajor-releases---710-rc2---710-or-800-rc3---800), force push `next` onto `main`) -> **Note** -> This actually also covers how to promote a prerelease to stable. This is basically any other releases than backported patch releases, which are described in the next section +The publish workflow runs in the "release" GitHub environment, which has the npm token needed to publish packages to the `@storybook` npm organization. For security reasons this environment can only be entered from the four "core" branches: `main`, `next`, `latest-release` and `next-release`. + +## 👉 How To Release + +This section describes what to do as a Releaser when it's time to release. Most of what's described here is also described in the release pull requests, in an attempt at making them a guide/to-do list for inexperienced Releasers. + +First, you locate the release pull request that has been prepared for the type of release you're about to release: + +- "Release: Prerelease 7.1.0-alpha.38" for prereleases +- "Release: Patch 7.0.23" for patch releases +- "Release: Merge patches to \`main\` (without version bump)" for patches without releases + +### Prereleases ### Patch releases @@ -330,7 +350,7 @@ This process is a bit different from the above because it needs to merge to the 6. When all is done, the description for the release PR will contain a list of PRs that couldn't be cherry picked, for the Releaser to manually do that and solve any merge conflicts. 7. An important additional step for the Releaser is to check that all PRs are actually patches/fixes, and not new features, and that everything actually works, given that some cherry picked PRs could rely on functionality not yet found in stable. -When the PR has been merged to `main-release` by the Releaser, after the usual publishing steps, the action will also label all patched PRs with "picked" so they are ignored for the next patch release. +When the PR has been merged to `latest-release` by the Releaser, after the usual publishing steps, the action will also label all patched PRs with "picked" so they are ignored for the next patch release. ### Patch releases to earlier versions - subset of `7.1.0-alpha.13` -> `6.5.14` From 1acb3ec756c70fc0574b80114da662b22d0b90c2 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 22 Jun 2023 01:11:51 +0200 Subject: [PATCH 06/17] phew, a lot of words --- CONTRIBUTING/RELEASING.md | 224 +++++++++++++++----- CONTRIBUTING/prerelease-workflow-inputs.png | Bin 0 -> 79439 bytes 2 files changed, 172 insertions(+), 52 deletions(-) create mode 100644 CONTRIBUTING/prerelease-workflow-inputs.png diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index b7a4b33b3ff9..538e2c30a074 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -12,9 +12,13 @@ - [Patch releases](#patch-releases) - [Publishing](#publishing) - [👉 How To Release](#-how-to-release) - - [Prereleases](#prereleases-1) - - [Patch releases](#patch-releases-1) - - [Making Manual Changes](#making-manual-changes) + - [1. Find the Prepared Pull Request](#1-find-the-prepared-pull-request) + - [2. Freeze the Pull Request](#2-freeze-the-pull-request) + - [3. QA Each Merged Pull Request](#3-qa-each-merged-pull-request) + - [4. Re-trigger the Workflow](#4-re-trigger-the-workflow) + - [5. Make Manual Changes](#5-make-manual-changes) + - [6. Merge](#6-merge) + - [7. See the "Publish" Workflow Finish](#7-see-the-publish-workflow-finish) - [Releasing Locally in Case of Emergency 🚨](#releasing-locally-in-case-of-emergency-) - [Canary Releases](#canary-releases) - [Versioning Scenarios](#versioning-scenarios) @@ -275,91 +279,207 @@ The publish workflow runs in the "release" GitHub environment, which has the npm This section describes what to do as a Releaser when it's time to release. Most of what's described here is also described in the release pull requests, in an attempt at making them a guide/to-do list for inexperienced Releasers. -First, you locate the release pull request that has been prepared for the type of release you're about to release: +The high-level workflow for a Releaser is: -- "Release: Prerelease 7.1.0-alpha.38" for prereleases -- "Release: Patch 7.0.23" for patch releases -- "Release: Merge patches to \`main\` (without version bump)" for patches without releases +1. Find the prepared pull request +2. Freeze the pull request +3. Make changes to merged pull requests (revert, rename, relabel) +4. Re-trigger the workflow to get changes from step 3 in +5. Make any manual changes necessary +6. Merge +7. See that the "publish" workflow finished successfully -### Prereleases +### 1. Find the Prepared Pull Request -### Patch releases +Find the release pull request that has been prepared for the type of release you're about to release: + +- "Release: Prerelease ``" for prereleases +- "Release: Patch ``" for patch releases +- "Release: Merge patches to `main` (without version bump)" for patches without releases + +Here's an example of such a pull request: https://github.com/storybookjs/storybook/pull/23148 + +### 2. Freeze the Pull Request + +Freeze the pull request by adding the "**freeze**" label to it. + +This instructs the preparation workflows to cancel and not do anything when new changes to `next` are merged. That way, you can make any changes you want to without needing to worry about other's work overriding your changes. + +Crucially this "**freeze**" label doesn't cancel the workflows when they are triggered manually, allowing you - the Releaser - to run the workflow even when it's frozen. + +### 3. QA Each Merged Pull Request + +You need to ensure that the release contains the correct stuff. The main things to check for are: + +1. Is the change appropriate for the version bump? + +This usually means checking if it's a breaking change that is not allowed in a minor prerelease, or if it's a new feature in a patch release. +If it's not appropriate, revert the pull request and notify the author. + +2. Is the pull request title correct? + +The title of pull requests are added to the user-facing changelogs, so they must be correct and understandable. They should follow the pattern "[Area]: [Summary]", where [Area] is which part of the repo that has been changed, and the summary is what has changed. + +It's common to confuse [Area] with labels, but they are not the same. Eg. the "**build**" label describes that the changes are only internal, but a "build" [Area] is _not_ correct. The area could be "Core" or "CI", but very rarely is the area being changed actually the "build" area. +It can be hard to pick an area when a pull request changes multiple places - this is often common when upgrading dependencies - so use your best judgement. There's no hard rule, but a good guideline is that the more precise it is, the more useful it is to read later. + +3. Is the pull request labeled correctly? + +Some labels have special meaning when it comes to releases. It's important that each pull request has labels that correctly identify the change, because labels can determine if a pull request is included in the changelog or not. A deeper explanation of this concept is given in the [Which changes are considered "releasable", and what does it mean?](#which-changes-are-considered-releasable-and-what-does-it-mean) section. + +4. Patches: has it already been released in a prerelease? + +If this is a patch release, it's best that you make sure that all pull requests have already been released in a prerelease. If some haven't, create a new prerelease first. + +There's no technical reason for this, it's purely a good practice to ensure that a change doesn't break a prerelease before releasing it to stable. + +### 4. Re-trigger the Workflow + +Any changes you made to pull requests' titles, labels or even reverts won't be reflected in the release pull request, because: + +A. It's hopefully frozen at this point +B. Even if it isn't, the workflow only triggers on pushes to `next`, it doesn't trigger when pull request meta data is changed + +Therefore if you've made any changes in step 3, you need to re-trigger the workflow manually to regenerate the changelog and the version bump. If you haven't made any changes previously this step can be skipped. -All changes should already have been prereleased +It's important to know that triggering the workflow will force push changes to the branch, so you need to do this before comitting any changes manually (which is the next step), as they will otherwise get overwritten. -### Making Manual Changes +> ** Warning ** +> When re-triggering the workflow, any new content merged to `next` will also become part of the release pull request. You can't just assume that you'll see the same content again but with fixes, as there could have been merged new content in since you froze the pull request. + +When triggering the workflows, always choose the `next` branch as the base unless you know exactly what you're doing. + +The workflows can be triggered here: + +- [Prepare prerelease PR](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) +- [Prepare patch PR](https://github.com/storybookjs/storybook/actions/workflows/prepare-patch-release.yml) + +Crucially for prereleases, this is also where you change the versioning strategy if you need something else than the default as described in [Preparing - Prereleases](#prereleases). When triggering the prerelease workflow manually, you can optionally add inputs: + +![Screenshot of triggering the prerelease workflow in GitHub Actions, with a form that shows a release type selector and a prerelease identifier text field](prerelease-workflow-inputs.png) + +See [Versioning Scenarios](#versioning-scenarios) for a description of each version bump scenario, how to activate it and what it does, and [Which combination of inputs creates the version bump I need?](#which-combination-of-inputs-creates-the-version-bump-i-need) for a detailed description of the workflow inputs. + +### 5. Make Manual Changes + +It's possible and perfectly valid to push manual changes directly on the release branch when needed. Maybe you need to alter the changelog in a way that can't be done purely automatical, or there's another critical change that is needed for the release to work. Any change you make will eventually be merged to `next|main` when the release has been published. + +This can be used as a quick and dirty way to fix a changelog without needing to change pull requests and waiting for workflows to finish. However it is recommended that you try to use the automated process as much as possible for this, to ensure that the information in GitHub is the single source of truth, and that pull requests and changelogs are in sync. + +### 6. Merge + +When you froze the pull request you also triggered a CI run on the branch. If it's green it's time to merge the pull request. + +If CI is failing for some reason consult with the rest of the core team. These release pull requests are almost exact copies of `next|main` so CI should only fail if they fail too. + +### 7. See the "Publish" Workflow Finish + +Merging the pull request will trigger [the publish workflow](https://github.com/storybookjs/storybook/actions/workflows/publish.yml), which does the final publishing. As a Releaser you're responsible for this to finish succesfully, so you should watch it till the end. +If it fails it will notify in Discord, so you can monitor that instead if you want to. + +Done! 🚀 ## Releasing Locally in Case of Emergency 🚨 +Things fail. Code breaks. Bugs exists. + +Sometimes we need an emergency escape hatch to release new fixes, even if the automation is broken. + +In those situations it's perfectly valid to run the whole release process locally instead of relying on the pull requests and workflows as usual. When doing this you don't need to create the pull requests either, or split preparation and publishing, you're free to do it all at the same time, but you need to make sure that you follow the correct branching strategy still. + +You need a token to the npm registry to be able to publish (set as `YARN_NPM_AUTH_TOKEN` below). Currently @shilman and @ndelangen holds the keys to that castle. + +You can always inspect the workflows to see exactly what they are running and copy that, but below is a general sequence of steps you can take to mimic the automated workflow. Feel free to diverge from this however you need to succeed. + +1. Create a new branch from either `next` (prereleases) or `main` (patches) +2. Get all tags: `git fetch --tags origin` +3. `cd scripts` +4. (if patch release) Cherry pick: + 1. `yarn release:pick-patches` + 2. manually cherry pick any patches necessary based on the previous output +5. Bump versions: `yarn release:version --verbose --release-type --pre-id ` +6. To see a list of changes (for your own todo list), run `yarn release:generate-pr-description --current-version --next-version --verbose` +7. Write changelogs: `yarn release:write-changelog --verbose` +8. `git add .` +9. Commit changes: `git commit -m "Bump version from to MANUALLY"` +10. Merge changes to the release branch: + 1. `git checkout <"latest-release" | "next-release">` + 2. `git merge ` + 3. `git push origin` +11. (if automatic publishing is still working it should kick in now and the rest of the steps can be skipped) +12. `cd ..` +13. Install dependencies: `yarn task --task=install --start-from=install` +14. Publish to the registry: `YARN_NPM_AUTH_TOKEN= yarn release:publish --tag <"next" OR "latest"> --verbose` +15. (if patch release) `yarn release:label-patches` +16. [Manually create a GitHub Release](https://github.com/storybookjs/storybook/releases/new) with a tag that is the new version and the target being `latest-release` or `next-release`. +17. Merge to core branch: + 1. `git checkout <"next"|"main">` + 2. `git merge <"next-release"|"latest-release">` + 3. `git push origin` +18. (if patch release) sync `CHANGELOG.md` to `next` with: + 1. `git checkout next` + 2. `git pull` + 3. `git checkout origin/main ./CHANGELOG.md` + 4. `git add ./CHANGELOG.md` + 5. `git commit -m "Update CHANGELOG.md for v"` + 6. `git push origin` + ## Canary Releases -Not implemented yet. +Not implemented yet. Still work in progress, stay tuned. ## Versioning Scenarios -There are five types of releases that will be handled somewhat differently, but following the overall same principles as described above. +There are six types of releases that are done somewhat differently, but following the overall same principles as described previously. ### Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13` -These happen multiple times a week - -1. A PR will automatically be opened from a fresh branch `version-from-7.1.0-alpha.16` to `next-release` on every push to `next`. If the PR is already open, it will be kept up-to-date with description changes and force pushing commits. This process can also be manually triggered if needed. -2. The PR will consist of: - 1. Version bumps in all `package.json`s and other files like `versions.ts` - 2. Changes to `CHANGELOG.md` generated as specced below - 3. Changes listed in the PR description, along with a checklist to go through manually -3. When we're ready to release, a Releaser will go through the check list: - 1. Freeze the PR by applying the "freeze" label on it, stopping any actions from modifying it further - 2. QA each PR that is part of the release: - 1. Is the changelog high quality - it's based on PR titles, which are usually bad. - 2. Change any PR titles necessary - 3. Check that each PR content is high quality, has it been tested, are we sure it's not a breaking change, etc. - 4. revert any bad PRs - 3. If necessary, manually trigger the workflow again, to reflect changes to PR titles and reverts (manual triggers should ignore the "freeze" label) - 4. Merge the PR to `next-release` -4. When the PR is merged, an action will: - 1. publish all packages - 2. create a GitHub Release - 3. tag the commit - 4. merge `next-release` back to `next`. If this causes a merge conflict, this will have to be done manually -5. ... the cycle starts over +**Cadence: Multiple times a week** + +This is the default strategy for prereleases, there's nothing special needed to trigger this scenario. ### Prerelease promotions - `7.1.0-alpha.13` -> `7.1.0-beta.0` -These happen once every 1-2 months +**Cadence: Once every 1-2 months** + +To promote a prerelease to a new prerelease ID, during the [Re-trigger the Workflow](#4-re-trigger-the-workflow) step, choose: -Same process as above, except before merging, the Releaser manually triggers the Action with a "tag: beta" input, that will change versions from the proposed `7.1.0-alpha.14` to `7.1.0-beta.0`. +- Release type: Prerelease +- Prerelease ID: The ID to promote to. Eg. for alpha -> beta, write "beta" ### Minor/major releases - `7.1.0-rc.2` -> `7.1.0` or `8.0.0-rc.3` -> `8.0.0` -These happen once every quarter +**Cadence: Once every quarter** -Same process as above, except before merging, the Releaser manually triggers the Action with a "tag: stable" input, that will change versions from the proposed `7.1.0-rc.3` to `7.1.0`. When the PR is merged, the action will do the usual publishing work, and **force merge `next` into `main`**. The following GitHub Action that triggers on a push to `next` will generate a release PR with `7.2.0-alpha.0`, to start the cycle over. +To promote a prerelease to a new prerelease ID, during the [Re-trigger the Workflow](#4-re-trigger-the-workflow) step, choose: -### Patch releases to stable - subset of `7.1.0-alpha.13` -> `7.0.14` +- Release type: Patch +- Prerelease ID: Leave empty + +The "Patch" release type ensures the current prerelease version gets promoted to a stable version without any major/minor/patch bumps. -These happen roughly every second week +This scenario is special in that it turns the `next` branch into a stable branch (until the next prerelease). Therefore this will also force push `next` to `main`, to ensure that `main` contains the latest stable release. Consequently, the history for `main` is lost. -This process is a bit different from the above because it needs to merge to the `main` branch and not `next`, but the principle is the same. +### Patch releases to stable - subset of `7.1.0-alpha.13` -> `7.0.14` -1. Any PR to `next` that needs to be patched back to stable, needs to have a "patch" label -2. On pushes to `next`, an action check for any such PRs with the "patch" label -3. It will create a release branch (`version-from-7.0.11`) and PR similar to the one for prereleases, except that it targets `main`. -4. Each "patch" PR that it finds it will attempt to cherry-pick to the `version-from-7.0.11` branch -5. Sometimes it might cause merge conflicts, in which case the PR will be skipped -6. When all is done, the description for the release PR will contain a list of PRs that couldn't be cherry picked, for the Releaser to manually do that and solve any merge conflicts. -7. An important additional step for the Releaser is to check that all PRs are actually patches/fixes, and not new features, and that everything actually works, given that some cherry picked PRs could rely on functionality not yet found in stable. +**Cadence: Every second week** -When the PR has been merged to `latest-release` by the Releaser, after the usual publishing steps, the action will also label all patched PRs with "picked" so they are ignored for the next patch release. +This is the default patch release scenario, that cherry picks patches to `main`. ### Patch releases to earlier versions - subset of `7.1.0-alpha.13` -> `6.5.14` -These happen 2-3 times a year +**Cadence: 2-3 times a year** -Given that this happens so rarely on a case by case basis, I'm okay with this being a completely manual process. The only thing we then need to keep in mind, is that all these versioning and publishing scripts needs to be executable locally, outside of a GitHub Action. +This happens so rarely on a case by case basis, so this is a completely manual process that isn't accounted for in the automation. The Releaser will find the git tag that matches the patch to bump, eg. `v6.5.14`, check it out, make the necessary changes and follow [the manual release process](#releasing-locally-in-case-of-emergency-🚨). ### Prerelease of upcoming patch release - `7.0.20` -> `7.0.21-alpha.0` +**Cadence: Very rare** + +In some cases a patch change is so big and complex that it makes sense to first release it as a prerelease of the current patch stable version to see if it works, before releasing it to stable shortly thereafter. + +There is no process defined for this. + ## FAQ ### How do I make changes to the release scripts? diff --git a/CONTRIBUTING/prerelease-workflow-inputs.png b/CONTRIBUTING/prerelease-workflow-inputs.png new file mode 100644 index 0000000000000000000000000000000000000000..55bb79e2b09cdfbaf920bde6215b34446344118b GIT binary patch literal 79439 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 ka*eik`Kt7o4`_!+ Date: Thu, 22 Jun 2023 02:07:23 +0200 Subject: [PATCH 07/17] answer FAQs --- CONTRIBUTING/RELEASING.md | 161 +++++++++++++++++++++++++++++++------- 1 file changed, 132 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 538e2c30a074..3895a1d3ce48 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -25,11 +25,12 @@ - [Prereleases - `7.1.0-alpha.12` -\> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13) - [Prerelease promotions - `7.1.0-alpha.13` -\> `7.1.0-beta.0`](#prerelease-promotions---710-alpha13---710-beta0) - [Minor/major releases - `7.1.0-rc.2` -\> `7.1.0` or `8.0.0-rc.3` -\> `8.0.0`](#minormajor-releases---710-rc2---710-or-800-rc3---800) + - [First prerelease of new major/minor - `7.1.0` -\> `7.2.0-alpha.0` or `8.0.0-alpha.0`](#first-prerelease-of-new-majorminor---710---720-alpha0-or-800-alpha0) - [Patch releases to stable - subset of `7.1.0-alpha.13` -\> `7.0.14`](#patch-releases-to-stable---subset-of-710-alpha13---7014) - [Patch releases to earlier versions - subset of `7.1.0-alpha.13` -\> `6.5.14`](#patch-releases-to-earlier-versions---subset-of-710-alpha13---6514) - [Prerelease of upcoming patch release - `7.0.20` -\> `7.0.21-alpha.0`](#prerelease-of-upcoming-patch-release---7020---7021-alpha0) - [FAQ](#faq) - - [How do I make changes to the release scripts?](#how-do-i-make-changes-to-the-release-scripts) + - [How do I make changes to the release tooling/process?](#how-do-i-make-changes-to-the-release-toolingprocess) - [Why do I need to re-trigger workflows to update the changelog?](#why-do-i-need-to-re-trigger-workflows-to-update-the-changelog) - [Which combination of inputs creates the version bump I need?](#which-combination-of-inputs-creates-the-version-bump-i-need) - [Which changes are considered "releasable", and what does it mean?](#which-changes-are-considered-releasable-and-what-does-it-mean) @@ -77,10 +78,8 @@ gitGraph commit branch latest-release branch next - checkout next commit branch next-release - checkout next-release commit commit tag: "7.1.0-alpha.18" checkout next @@ -105,36 +104,32 @@ gitGraph commit branch latest-release branch next - checkout next commit branch next-release branch new-feature - checkout new-feature commit commit checkout next merge new-feature commit branch some-patched-bugfix - checkout some-patched-bugfix commit commit id: "patched-bugfix" checkout next merge some-patched-bugfix commit - branch version-from-7.1.0-alpha.20 - checkout version-from-7.1.0-alpha.20 + branch version-prerelease-from-7.1.0-alpha.20 commit checkout next-release - merge version-from-7.1.0-alpha.20 tag: "7.1.0-alpha.21" + merge version-prerelease-from-7.1.0-alpha.20 tag: "7.1.0-alpha.21" checkout next merge next-release checkout main - branch version-from-7.0.18 + branch version-patch-from-7.0.18 commit cherry-pick id: "patched-bugfix" checkout latest-release - merge version-from-7.0.18 tag: "7.0.19" + merge version-patch-from-7.0.18 tag: "7.0.19" checkout main merge latest-release ``` @@ -169,7 +164,7 @@ The default versioning strategy is to bump the current prerelease number, as des Prerelease PRs are only prepared if there are actual changes to release, otherwise, the workflow will be canceled. `next` can have new content which is only labeled with "build" or "documentation", which isn't user-facing so it's [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean). In that case, it doesn't make sense to create a release, as it won't bump versions nor write changelogs so it would just merge the same content back to `next`. This is explained more deeply in [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared). -The preparation workflow will create a new branch from `next` called `version-from-`, and open a pull request that targets `next-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `next-release` into `next`. +The preparation workflow will create a new branch from `next` called `version-prerelease-from-`, and open a pull request that targets `next-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `next-release` into `next`. Here's an example of a workflow where a feature and a bugfix have been created and then released to a new `7.1.0-alpha.29` version. All the highlighted commits (square dots) are the ones that will be considered when generating the changelog. @@ -178,27 +173,23 @@ Here's an example of a workflow where a feature and a bugfix have been created a gitGraph commit branch next-release - checkout next-release commit tag: "7.1.0-alpha.28" checkout next merge next-release commit type: HIGHLIGHT id: "direct commit" branch new-feature - checkout new-feature commit commit checkout next merge new-feature type: HIGHLIGHT branch some-bugfix - checkout some-bugfix commit checkout next merge some-bugfix type: HIGHLIGHT - branch version-from-7.1.0-alpha.28 - checkout version-from-7.1.0-alpha.28 + branch version-prerelease-from-7.1.0-alpha.28 commit id: "bump version" checkout next-release - merge version-from-7.1.0-alpha.28 tag: "7.1.0-alpha.29" + merge version-prerelease-from-7.1.0-alpha.28 tag: "7.1.0-alpha.29" checkout next merge next-release ``` @@ -216,7 +207,7 @@ On the surface, it might not make sense to create a new patch release if the cha The preparation workflow sequentially cherry-picks each patch pull request to its branch. Sometimes this cherry-picking fails because of conflicts or for other reasons, in which case it ignores it and moves on to the next. All the failing cherry-picks are finally listed in the release pull request's description, for the Releaser to manually cherry-pick during the release process. This problem occurs more often the more `main` and `next` diverges, ie. the longer it has been since a stable major/minor release. -Similar to the prerelease flow, the preparation workflow for patches will create a new branch from `main` called `version-from-`, and open a pull request that targets `latest-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `latest-release` into `main`. +Similar to the prerelease flow, the preparation workflow for patches will create a new branch from `main` called `version-patch-from-`, and open a pull request that targets `latest-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `latest-release` into `main`. Here's an example of a workflow where a feature and two bug fixes have been merged to `next`. Only the bug fixes have the "**patch**" label, so only those two go into the new `7.0.19` release. Note that while the diagram shows the commits _on_ the bugfix branches being cherry-picked, it's actually their merge commits to `next` that gets picked - this is a limitation of mermaid graphs. @@ -232,28 +223,25 @@ gitGraph checkout next commit branch some-patched-bugfix - checkout some-patched-bugfix commit commit id: "patch1" checkout next merge some-patched-bugfix branch new-feature - checkout new-feature commit checkout next merge new-feature branch other-patched-bugfix - checkout other-patched-bugfix commit id: "patch2" checkout next merge other-patched-bugfix checkout main - branch version-from-7.0.18 + branch version-patch-from-7.0.18 cherry-pick id: "patch1" cherry-pick id: "patch2" commit id: "version bump" checkout latest-release - merge version-from-7.0.18 tag: "v7.0.19" + merge version-patch-from-7.0.18 tag: "v7.0.19" checkout main merge latest-release ``` @@ -430,7 +418,7 @@ Not implemented yet. Still work in progress, stay tuned. ## Versioning Scenarios -There are six types of releases that are done somewhat differently, but following the overall same principles as described previously. +There are seven types of releases that are done somewhat differently, but following the overall same principles as described previously. ### Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13` @@ -460,6 +448,15 @@ The "Patch" release type ensures the current prerelease version gets promoted to This scenario is special in that it turns the `next` branch into a stable branch (until the next prerelease). Therefore this will also force push `next` to `main`, to ensure that `main` contains the latest stable release. Consequently, the history for `main` is lost. +### First prerelease of new major/minor - `7.1.0` -> `7.2.0-alpha.0` or `8.0.0-alpha.0` + +**Cadence: Once every quarter** + +This is the first prerelease after a stable major/minor has been released. In this case the default versioning strategy for prereleases won't work, because it will do `7.1.0` -> `7.1.1-0`. You need to use the workflow inputs to bump the major/minor correctly: + +- Release type: Premajor for `8.0.0-alpha.0` or Preminor for `7.2.0-alpha.0` +- Prerelease ID: "alpha" + ### Patch releases to stable - subset of `7.1.0-alpha.13` -> `7.0.14` **Cadence: Every second week** @@ -482,20 +479,126 @@ There is no process defined for this. ## FAQ -### How do I make changes to the release scripts? +### How do I make changes to the release tooling/process? -(patch script changes back to main, either manually or via the patching flow) +The whole process is based on [GitHub Action workflows](../.github/workflows/) and [scripts](../scripts/release/), so it's possible to change them as long as you know what you're doing. + +The short answer to "how", is to make changes as a regular pull request that is also patched back to `main`. + +There's a longer answer too, but it's pretty confusing: + +Depending on which workflow runs, the scripts are either being runned from `main` or `next`, so if you're making changes to a release script, you want that change in `main` as well for it to have an affect on patch releases. Patching the change back during the normal workflow will mean that the first patch release _will not_ use your change, since it runs before the change has been patched. +If you need it to run as part of the very next patch workflow, you need to manually cherry pick you change to `main`, so that it gets used by the automation immediately. + +The case isn't the same for changes to workflow files, as they almost always run from `next`, so you don't need to patch them back, but it doesn't hurt. **We recommend always patching any changes back anyway, to be consistent**. The "publish" workflow _does_ run from `latest-release` and `next-release`, so you want changes to _that_ to always be patched back. 🙃 ### Why do I need to re-trigger workflows to update the changelog? +Any changes you made to pull requests' titles, labels or even reverts won't be reflected in the release pull request, because: + +A. It's hopefully frozen +B. Even if it isn't, the workflow only triggers on pushes to `next`, it doesn't trigger when pull request meta data is changed + +Therefore if you've made any changes to pull requests, you need to re-trigger the workflow manually to regenerate the changelog and the version bump. + +You could also just make the changes to the changelog manually, which isn't totally out of the question, but it means that the pull requests and their title/lables are no longer the single source of truth. + ### Which combination of inputs creates the version bump I need? -Link to tests, and to version strategies above. +Each versioning scenario including how to trigger it with inputs is described in [Versioning Scenarios](#versioning-scenarios). + +You can also see [the tests for the versioning script](https://github.com/storybookjs/storybook/blob/next/scripts/release/__tests__/version.test.ts#L137-L161) to see which inputs creates which outputs. ### Which changes are considered "releasable", and what does it mean? -link to the enums +A specific set of labels define which kind of change a pull request is, and if it is a "releasable" change or not. + +Releasable changes will appear in the changelog and will trigger version bumps, while unreleasable changes will not. + +The exact list of labels and their type is written [here](https://github.com/storybookjs/storybook/blob/next/scripts/release/utils/get-changes.ts#L9-L21). + +Currently releasable labels are: + +- BREAKING CHANGE +- feature request +- bug +- maintenance +- dependencies + +And unreleasable labels are: + +- documentation +- build + +If a pull request doesn't have any of the above labels at the time of release, it is considered an unreleasable change. + +unreleasable changes are changes that don't affect the user through releases. Documentation-only changes are unreleasable, because they're not part of packages and they don't change behavior. Similarly "build" changes are only internal-facing and doesn't change behavior. This could be tests, CI, etc. ### Why are no release PRs being prepared? +This most likely happens because `next` only contains [unreleasable changes](#which-changes-are-considered-releasable-and-what-does-it-mean), which causes the preparation workflow to cancel itself. That's because it doesn't make sense to prepare a new release if all the changes are unreleasable, as that wouldn't bump the version nor write a new changelog entry, so "releasing" it would just merge it back to `next` without any differences. + +You can always see the workflows and if they've been cancelled [here for prereleases](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and [here for patch releases](https://github.com/storybookjs/storybook/actions/workflows/prepare-patch-release.yml) + ### Why do we need separate release branches? + +A simpler branching approach would be to merge the versioning branches back to `main` or `next` instead of `latest-release` or `next-release`, and then trigger the publishing directly on that branch. After all that is what tools like [Changesets](https://github.com/changesets/changesets) does. + +The problem with that, is that you could end up publishing changes that wasn't part of the prepared pull request, and thus not part of QA nor the changelog. + +Take the following scenario, where the Releaser is creating a new release with the frozen branch, and another team member merges a new pull request - "some-simultaneous-bugfix - to `next` _during_ the QA steps: + +```mermaid +%%{init: { 'gitGraph': { 'mainBranchName': 'next' } } }%% +gitGraph + commit type: HIGHLIGHT + branch new-feature + commit + commit + checkout next + merge new-feature type: HIGHLIGHT + branch some-simultaneous-bugfix + commit + checkout next + branch version-prerelease-from-7.1.0-alpha.28 + commit id: "bump version" + checkout next + merge some-simultanous-bugfix type: HIGHLIGHT id: "whoops!" + merge version-prerelease-from-7.1.0-alpha.28 tag: "v7.1.0-alpha.29" + +``` + +When publishing at the last commit with tag `v7.1.0-alpha.29`, it will publish whatever the content is at that point (all the square dots), which includes the "whoops!" commit from merging the bugfix. But the bugfix was never part of the release pull request because it got prepared before the bugfix was merged in. It essentially becomes a hidden change, hidden from the Releaser and the changelog. + +If we instead publish from `next-release` and then merge to `next`, will see that the bugfix won't be part of the current release, but the next one: + +```mermaid +%%{init: { 'gitGraph': { 'mainBranchName': 'next' } } }%% +gitGraph + commit type: HIGHLIGHT + branch next-release + branch new-feature + commit + commit + checkout next + merge new-feature type: HIGHLIGHT + branch some-simultanous-bugfix + commit + checkout next + branch version-prerelease-from-7.1.0-alpha.28 + commit id: "bump version" + checkout next + merge some-simultanous-bugfix id: "whoops!" + checkout next-release + merge version-prerelease-from-7.1.0-alpha.28 tag: "v7.1.0-alpha.29" + checkout next + merge next-release + branch version-prerelease-from-7.1.0-alpha.29 + commit id: "bump version again" + checkout next-release + merge version-prerelease-from-7.1.0-alpha.29 tag: "v7.1.0-alpha.30" + checkout next + merge next-release +``` + +This is because the way that "unreleased" changes are found is to list all the commits that are part of the current history of `HEAD`, _except_ for the commits that are part of the history of the latest version tag. And since the bugfix is not part of the history of the previous version, it will be included. From dfbd93de4a5714ac9f36735d63731c1895af3408 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 22 Jun 2023 02:36:30 +0200 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=A4=96=20Improve=20Writing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING/RELEASING.md | 302 +++++++++++++++----------------------- 1 file changed, 116 insertions(+), 186 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 3895a1d3ce48..cfbbb8a28fc5 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -1,17 +1,17 @@ # Releasing > **Note** -> This document is only really relevant for any of the core team members that actually have permissions to release new versions of Storybook. Feel free to read it out of interest or to suggest changes, but as a regular contributor or maintainer you don't have to care about this. +> This document is relevant only for core team members who have permissions to release new versions of Storybook. Regular contributors and maintainers can read it for interest or to suggest changes, but they don't have to. ## Table of Contents - [Introduction](#introduction) - [Branches](#branches) -- [The Release Pull Requests](#the-release-pull-requests) +- [Release Pull Requests](#release-pull-requests) - [Prereleases](#prereleases) - - [Patch releases](#patch-releases) + - [Patch Releases](#patch-releases) - [Publishing](#publishing) -- [👉 How To Release](#-how-to-release) +- [👉 How to Release](#-how-to-release) - [1. Find the Prepared Pull Request](#1-find-the-prepared-pull-request) - [2. Freeze the Pull Request](#2-freeze-the-pull-request) - [3. QA Each Merged Pull Request](#3-qa-each-merged-pull-request) @@ -19,7 +19,7 @@ - [5. Make Manual Changes](#5-make-manual-changes) - [6. Merge](#6-merge) - [7. See the "Publish" Workflow Finish](#7-see-the-publish-workflow-finish) -- [Releasing Locally in Case of Emergency 🚨](#releasing-locally-in-case-of-emergency-) +- [Releasing Locally in an Emergency 🚨](#releasing-locally-in-an-emergency-) - [Canary Releases](#canary-releases) - [Versioning Scenarios](#versioning-scenarios) - [Prereleases - `7.1.0-alpha.12` -\> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13) @@ -39,38 +39,31 @@ ## Introduction -This document describes how the release process for the Storybook monorepo is set up. There are mainly two different types of releases: +This document explains the release process for the Storybook monorepo. There are two types: 1. Prereleases and major/minor releases - releasing any content that is on the `next` branch 2. Patch releases - picking any content from `next` to `main`, that needs to be patched back to the current stable minor release The release process is based on automatically created "Release Pull Requests", that when merged will trigger a new version to be released. A designated Releaser - can be a different team member from time to time - will go through the release process in the current Release PR. -The process is implemented with a set of NodeJS scripts at [`scripts/release`](../scripts/release/), invoked by three GitHub Actions workflows: +A designated Releaser - which may change - will go through the release process in the current Release PR. This process is implemented with NodeJS scripts in [`scripts/release`](../scripts/release/) and three GitHub Actions workflows: - [Prepare Prerelease PR](../.github/workflows/prepare-prerelease.yml) - [Prepare Patch PR](../.github/workflows/prepare-patch-release.yml) - [Publish](../.github/workflows/publish.yml) > **Note** -> Throughout this document the distinction between **patch** release and **prereleases** are made. This is a simplification, since stable major and minor releases work the exact same way as **prereleases**, so that term covers those two cases as well. The distinction reflects the difference between releases updating the existing minor version on `main` with a new patch, or releasing a new minor/major/prerelease from `next`. +> This document distinguishes between **patch** releases and **prereleases**. This is a simplification; stable major and minor releases work the same way as prereleases. The distinction reflects the difference between patching an existing minor version on `main` or releasing a new minor/major/prerelease from `next`. ### Branches -To understand how all of this fits together in the repository, it's important to understand the branching strategy used. +To understand the release structure, it's important to know the branching strategy used. All development is done on the `next` branch, where new features and bug fixes are added. This branch contains content to be released in the next prerelease (eg. `v7.1.0-alpha.22`). -All development is done against the default `next` branch, and any new features/bug fixes will almost always target that branch. The `next` branch contains the content ready to be released in the next prerelease. Any upcoming prerelease (eg. `v7.1.0-alpha.22`) will release the content of `next`. +The `main` branch contains the content for the current stable release (eg. `v7.0.20`). When changes need to be made to both the next major/minor release and the current patch release (bug fixes or small improvements), they are made to `next`. If the change needs to be patched back to the current minor version (eg. from `7.1.0-alpha.20` to `7.0.18`), the PR containing the fix is labeled with the **"patch"** label, so the release workflow can pick it up. This ensures changes are tested in a prerelease before being released to stable. -The `main` branch contains the content for the current stable release, eg. `v7.0.20`. +The actual (pre)releases are not made from `next` nor `main`, but from `next-release` and `latest-release` respectively. This indirection is explained in [the "Why do we need separate release branches?" section](#why-do-we-need-separate-release-branches) below. -Sometimes we're making changes that both need to be in the next major/minor release, and in the current patch release. That might be bug fixes or small quality-of-life improvements. Making all changes target `next` ensures that the bugfix will land in the upcoming prerelease. To also get the change patched back to the current minor version (eg. from `7.1.0-alpha.20` to `7.0.18`), the PR containing the fix will get the **"patch"** label. That label tells the release workflow that it should pick that PR for the next patch release. -This structure ensures that the changes are safely tried out in a prerelease, before being released to stable. - -There are many nuances to the process defined above, which are described in greater detail in [the "Versioning Scenarios" section](#versioning-scenarios) below. - -The actual (pre)releases aren't actually released from `next` nor `main`, but from `next-release` and `latest-release` respectively. That means that `next-release` and `latest-release` follow `next` and `main` closely, but they are not always in complete sync - which is on purpose. The reason for this indirection is described in [the "Why do we need separate release branches?" section](#why-do-we-need-separate-release-branches) below. - -At a high level, the branches in the monorepo can be described in this diagram (greatly simplified): +The branches in the monorepo can be summarized in this diagram (simplified): ```mermaid %%{init: { 'gitGraph': { 'showCommitLabel': false } } }%% @@ -99,74 +92,41 @@ gitGraph merge latest-release ``` -```mermaid -gitGraph - commit - branch latest-release - branch next - commit - branch next-release - branch new-feature - commit - commit - checkout next - merge new-feature - commit - branch some-patched-bugfix - commit - commit id: "patched-bugfix" - checkout next - merge some-patched-bugfix - commit - branch version-prerelease-from-7.1.0-alpha.20 - commit - checkout next-release - merge version-prerelease-from-7.1.0-alpha.20 tag: "7.1.0-alpha.21" - checkout next - merge next-release - checkout main - branch version-patch-from-7.0.18 - commit - cherry-pick id: "patched-bugfix" - checkout latest-release - merge version-patch-from-7.0.18 tag: "7.0.19" - checkout main - merge latest-release -``` +## Release Pull Requests -## The Release Pull Requests +Two GitHub Actions workflows automatically create release pull requests, one for each type of release. These pull requests act as the "interface" for the Releaser to create a new release. Although the behavior between the two is similar, some minor differences exist, as described in the subsections below. -The release pull requests are automatically created by two different GitHub Actions workflows, one for each type of release. These pull requests are the "interface" for the Releaser to create a new release. The behavior between the two is very similar, with some minor differences described in the subsections. The high-level flow is: +The high-level flow is: -1. When a PR is merged to `next` (or a commit is pushed), both release pull requests are (re)generated -2. They create a new branch - `version-(patch|prerelease)-from-` -3. Bump all versions according to the version strategy (more on that below) -4. Update `CHANGELOG(.prerelease).md` with all changes detected -5. Commit everything -6. **Force push** -7. Open/edit pull request towards `next-release` or `latest-release` +1. When a PR is merged to `next` (or a commit is pushed), both release pull requests are (re)generated. +2. They create a new branch - `version-(patch|prerelease)-from-`. +3. They bump all versions according to the version strategy. +4. They update `CHANGELOG(.prerelease).md` with all changes detected. +5. They commit everything. +6. They **force push**. +7. They open/edit a pull request towards `next-release` or `latest-release`. -A few important things to note in this flow: +A few key points to note in this flow: -- The PRs are regenerated on any changes to `next`, and a re-generation can be manually triggered as well (more on that in [How To Release](#how-to-release)) -- The changes are force pushed to the branch. Combining that with the bullet above, it means that if you commit any manual changes on the release branch before merging it, they risk being overwritten if a new change is merged to `next` by someone else, triggering the workflow. To mitigate this, [apply the **"freeze"** label to the pull request](#how-to-release). -- The version bumps and changelogs are committed during the preparation, but the packages are _not actually published_ until [later](#publishing). This is a non-standard paradigm, where usually bumping versions and publishing packages happens at the same time. -- The release pull requests don't target their working branches (`next` and `main`), but rather the release-focused `next-release` and `latest-release`. +- The PRs are regenerated on any changes to `next`, or can be manually triggered (see [the Re-trigger the Workflow section](#4-re-trigger-the-workflow)). +- The changes are force pushed to the branch, so any manual changes on the release branch before merging risk being overwritten if someone else merges a new change to `next`, triggering the workflow. To avoid this, apply the **"freeze"** label to the pull request. +- The version bumps and changelogs are committed during the preparation, but the packages are not published until later. +- The release pull requests don't target their working branches (`next` and `main`), but rather `next-release` and `latest-release`. ### Prereleases > **Note** > Workflow: [`prepare-prerelease.yml`](../.github/workflows/prepare-prerelease.yml) -Prereleases are prepared with all content from `next`. The changelog is generated by using the git history, and looking up all the commits and PRs between the currently released prerelease (on `next-release`) and `HEAD` of `next`. +Prereleases are prepared with all content from the `next` branch. The changelog is generated by examining the git history, and looking up all the commits and pull requests between the current prerelease (on `next-release`) and `HEAD` of `next`. -The default versioning strategy is to bump the current prerelease number, as described in [Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13). If there is no prerelease number (ie. we just released a new stable minor/major) it will add one to a patch bump, so it would go from `7.2.0` to `7.2.1-0` per default. +The default versioning strategy is to increase the current prerelease number, as described in [Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13). If there is no prerelease number (i.e., we just released a new stable minor/major version), it will add one to a patch bump, so it would go from `7.2.0` to `7.2.1-0` by default. -Prerelease PRs are only prepared if there are actual changes to release, otherwise, the workflow will be canceled. `next` can have new content which is only labeled with "build" or "documentation", which isn't user-facing so it's [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean). In that case, it doesn't make sense to create a release, as it won't bump versions nor write changelogs so it would just merge the same content back to `next`. This is explained more deeply in [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared). +Prerelease PRs are only created if there are actual changes to release. Content labeled with "build" or "documentation" is [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean) and is not user-facing, so it doesn't make sense to create a release. This is explained in more detail in [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared). -The preparation workflow will create a new branch from `next` called `version-prerelease-from-`, and open a pull request that targets `next-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `next-release` into `next`. +The preparation workflow will create a new branch from `next`, called `version-prerelease-from-`, and open a pull request targeting `next-release`. When the Releaser merges it, the [publish workflow](#publishing) will merge `next-release` into `next`. -Here's an example of a workflow where a feature and a bugfix have been created and then released to a new `7.1.0-alpha.29` version. All the highlighted commits (square dots) are the ones that will be considered when generating the changelog. +Here's an example of a workflow where a feature and a bugfix have been created and then released to a new `7.1.0-alpha.29` version. All the commits highlighted with square dots are the ones that will be considered when generating the changelog. ```mermaid %%{init: { 'gitGraph': { 'mainBranchName': 'next' } } }%% @@ -194,22 +154,20 @@ gitGraph merge next-release ``` -### Patch releases +### Patch Releases > **Note** > Workflow: [`prepare-patch-release.yml`](../.github/workflows/prepare-patch-release.yml) -Patch releases are prepared by [cherry-picking](https://www.atlassian.com/git/tutorials/cherry-pick) any merged, unreleased pull requests to `next` that have the "**patch**" label applied. The merge commit of said pull requests are cherry-picked. +Patch releases are created by [cherry-picking](https://www.atlassian.com/git/tutorials/cherry-pick) any merged, unreleased pull requests that have the "**patch**" label applied to the `next` branch. The merge commit of said pull requests are cherry-picked. -Sometimes it's desired to pick pull requests back to `main` even if they are [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean), so opposite of the prerelease preparation, patch releases will _not cancel_ if the content is not releasable. -On the surface, it might not make sense to create a new patch release if the changes are only for documentation and/or internal build systems. But getting the changes back to `main` is the only way to get the documentation deployed to the production docs site. You also might want to cherry-pick changes to internal CI to fix issues or similar. Both of these cases are valid scenarios where you want to cherry-pick the changes without being blocked on "releasable" content to be ready. In these cases where all cherry picks are non-releasable, the preparation workflow creates a "merging" pull request instead of a "releasing" pull request. In this state, it doesn't bump versions and it doesn't update changelogs, it just cherry-picks the changes and allows you to merge them into `latest-release` -> `main`. +Sometimes it is desired to pick pull requests back to `main` even if they are not considered "releasable". Unlike prerelease preparation, patch releases will not be canceled if the content is not releasable. It might not make sense to create a new patch release if the changes are only for documentation and/or internal build systems. However, getting the changes back to `main` is the only way to deploy the documentation to the production docs site. You may also want to cherry-pick changes to internal CI to fix issues. These are valid scenarios where you want to cherry-pick the changes without being blocked on "releasable" content. In these cases, where all cherry picks are non-releasable, the preparation workflow creates a "merging" pull request instead of a "releasing" pull request. This pull request does not bump versions or update changelogs; it just cherry-picks the changes and allows you to merge them into `latest-release` -> `main`. -The preparation workflow sequentially cherry-picks each patch pull request to its branch. Sometimes this cherry-picking fails because of conflicts or for other reasons, in which case it ignores it and moves on to the next. All the failing cherry-picks are finally listed in the release pull request's description, for the Releaser to manually cherry-pick during the release process. -This problem occurs more often the more `main` and `next` diverges, ie. the longer it has been since a stable major/minor release. +The preparation workflow sequentially cherry-picks each patch pull request to its branch. If this cherry-picking fails due to conflicts or other reasons, it is ignored and the next pull request is processed. All failing cherry-picks are listed in the release pull request's description, for the Releaser to manually cherry-pick during the release process. This problem occurs more often when `main` and `next` diverge, i.e. the longer it has been since a stable major/minor release. -Similar to the prerelease flow, the preparation workflow for patches will create a new branch from `main` called `version-patch-from-`, and open a pull request that targets `latest-release`. When that is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `latest-release` into `main`. +Similar to the prerelease flow, the preparation workflow for patches will create a new branch from `main` called `version-patch-from-`, and open a pull request that targets `latest-release`. When the pull request is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `latest-release` into `main`. -Here's an example of a workflow where a feature and two bug fixes have been merged to `next`. Only the bug fixes have the "**patch**" label, so only those two go into the new `7.0.19` release. Note that while the diagram shows the commits _on_ the bugfix branches being cherry-picked, it's actually their merge commits to `next` that gets picked - this is a limitation of mermaid graphs. +Here is an example of a workflow where a feature and two bug fixes have been merged to `next`. Only the bug fixes have the "**patch**" label, so only those two go into the new `7.0.19` release. Note that it is the merge commits to `next` that are cherry-picked, not the commits on the bugfix branches. ```mermaid gitGraph @@ -251,21 +209,21 @@ gitGraph > **Note** > Workflow: [`publish.yml`](../.github/workflows/publish.yml) -When either a prerelease or a patch release branch is merged to `main|next-release`, the publishing workflow is triggered. This workflow does the following on a high level: +When either a prerelease or a patch release branch is merged into `main` or `next-release`, the publishing workflow is triggered. This workflow performs the following tasks: -1. Install dependencies and build all packages -2. Publish packages to npm -3. (If this is a patch release, add the "**picked**" label to all picked pull requests) -4. Create a new GitHub Release - also creating a version tag at the release branch (`latest-release` or `next-release`) -5. Merge the release branch into the core branch (`main` or `next`) -6. (If this is a patch release, copy the `CHANGELOG.md` changes from `main` to `next`) -7. (If this is [a promotion from a prerelease to a stable release](#minormajor-releases---710-rc2---710-or-800-rc3---800), force push `next` onto `main`) +1. Install dependencies and build all packages. +2. Publish packages to npm. +3. (If this is a patch release, add the "**picked**" label to all relevant pull requests.) +4. Create a new GitHub Release, including a version tag in the release branch (`latest-release` or `next-release`). +5. Merge the release branch into the core branch (`main` or `next`). +6. (If this is a patch release, copy the `CHANGELOG.md` changes from `main` to `next`.) +7. (If this is [a promotion from a prerelease to a stable release](#minormajor-releases---710-rc2---710-or-800-rc3---800), force push `next` to `main`.) -The publish workflow runs in the "release" GitHub environment, which has the npm token needed to publish packages to the `@storybook` npm organization. For security reasons this environment can only be entered from the four "core" branches: `main`, `next`, `latest-release` and `next-release`. +The publish workflow runs in the "release" GitHub environment, which has the npm token required to publish packages to the `@storybook` npm organization. For security reasons, this environment can only be accessed from the four "core" branches: `main`, `next`, `latest-release` and `next-release`. -## 👉 How To Release +## 👉 How to Release -This section describes what to do as a Releaser when it's time to release. Most of what's described here is also described in the release pull requests, in an attempt at making them a guide/to-do list for inexperienced Releasers. +This section explains what a Releaser should do when it's time to release. The steps are also outlined in the release pull requests, to provide guidance for inexperienced Releasers. The high-level workflow for a Releaser is: @@ -273,69 +231,63 @@ The high-level workflow for a Releaser is: 2. Freeze the pull request 3. Make changes to merged pull requests (revert, rename, relabel) 4. Re-trigger the workflow to get changes from step 3 in -5. Make any manual changes necessary +5. Make any manual changes needed 6. Merge -7. See that the "publish" workflow finished successfully +7. Check that the "publish" workflow has finished successfully ### 1. Find the Prepared Pull Request -Find the release pull request that has been prepared for the type of release you're about to release: +Look for the release pull request that has been prepared for the type of release you're about to release: - "Release: Prerelease ``" for prereleases - "Release: Patch ``" for patch releases - "Release: Merge patches to `main` (without version bump)" for patches without releases -Here's an example of such a pull request: https://github.com/storybookjs/storybook/pull/23148 +For example: https://github.com/storybookjs/storybook/pull/23148 ### 2. Freeze the Pull Request -Freeze the pull request by adding the "**freeze**" label to it. - -This instructs the preparation workflows to cancel and not do anything when new changes to `next` are merged. That way, you can make any changes you want to without needing to worry about other's work overriding your changes. +Add the "**freeze**" label to the pull request. This will stop the preparation workflows from running when new changes to `next` are merged. This allows you to make changes without worrying about other people's work overriding yours. -Crucially this "**freeze**" label doesn't cancel the workflows when they are triggered manually, allowing you - the Releaser - to run the workflow even when it's frozen. +The "**freeze**" label does not cancel the workflows when they are triggered manually, so you can still run the workflow. ### 3. QA Each Merged Pull Request -You need to ensure that the release contains the correct stuff. The main things to check for are: +You need to check that the release contains the correct stuff. The main things to check for are: -1. Is the change appropriate for the version bump? +1. Is the change suitable for the version bump? -This usually means checking if it's a breaking change that is not allowed in a minor prerelease, or if it's a new feature in a patch release. -If it's not appropriate, revert the pull request and notify the author. +For example, check if it's a breaking change that isn't allowed in a minor prerelease, or if it's a new feature in a patch release. If it's not suitable, revert the pull request and notify the author. 2. Is the pull request title correct? -The title of pull requests are added to the user-facing changelogs, so they must be correct and understandable. They should follow the pattern "[Area]: [Summary]", where [Area] is which part of the repo that has been changed, and the summary is what has changed. +The title of pull requests is added to the user-facing changelogs, so it must be accurate and understandable. It should follow the pattern "[Area]: [Summary]", where [Area] is the part of the repo that has been changed, and the summary is what has changed. -It's common to confuse [Area] with labels, but they are not the same. Eg. the "**build**" label describes that the changes are only internal, but a "build" [Area] is _not_ correct. The area could be "Core" or "CI", but very rarely is the area being changed actually the "build" area. -It can be hard to pick an area when a pull request changes multiple places - this is often common when upgrading dependencies - so use your best judgement. There's no hard rule, but a good guideline is that the more precise it is, the more useful it is to read later. +It's easy to confuse [Area] with labels, but they are not the same. For example, the "**build**" label indicates that the changes are internal, but a "build" [Area] is _not_ correct. The area could be "Core" or "CI", but rarely is the area being changed actually the "build" area. +If a pull request changes multiple places, it can be hard to choose an area - this is often the case when upgrading dependencies - so use your best judgement. There's no hard rule, but a good guideline is that the more precise it is, the more useful it is to read later. 3. Is the pull request labeled correctly? -Some labels have special meaning when it comes to releases. It's important that each pull request has labels that correctly identify the change, because labels can determine if a pull request is included in the changelog or not. A deeper explanation of this concept is given in the [Which changes are considered "releasable", and what does it mean?](#which-changes-are-considered-releasable-and-what-does-it-mean) section. +Some labels have specific meanings when it comes to releases. It's important that each pull request has labels that accurately describe the change, as labels can determine if a pull request is included in the changelog or not. This is explained further in the [Which changes are considered "releasable", and what does it mean?](#which-changes-are-considered-releasable-and-what-does-it-mean) section. 4. Patches: has it already been released in a prerelease? -If this is a patch release, it's best that you make sure that all pull requests have already been released in a prerelease. If some haven't, create a new prerelease first. +If this is a patch release, make sure that all pull requests have already been released in a prerelease. If some haven't, create a new prerelease first. -There's no technical reason for this, it's purely a good practice to ensure that a change doesn't break a prerelease before releasing it to stable. +This is not a technical requirement, but it's a good practice to ensure that a change doesn't break a prerelease before releasing it to stable. ### 4. Re-trigger the Workflow -Any changes you made to pull requests' titles, labels or even reverts won't be reflected in the release pull request, because: +Any changes made to pull requests' titles, labels or even reverts won't be reflected in the release pull request because it's hopefully frozen at this point. Even if it isn't, the workflow only triggers on pushes to `next`, not when pull request meta data is changed. -A. It's hopefully frozen at this point -B. Even if it isn't, the workflow only triggers on pushes to `next`, it doesn't trigger when pull request meta data is changed +Therefore, if any changes were made in step 3, you need to re-trigger the workflow manually to regenerate the changelog and the version bump. If no changes were made, this step can be skipped. -Therefore if you've made any changes in step 3, you need to re-trigger the workflow manually to regenerate the changelog and the version bump. If you haven't made any changes previously this step can be skipped. +It's important to remember that triggering the workflow will force push changes to the branch, so it must be done before committing any changes manually (the next step). Otherwise, these will be overwritten. -It's important to know that triggering the workflow will force push changes to the branch, so you need to do this before comitting any changes manually (which is the next step), as they will otherwise get overwritten. +> **Warning** +> When re-triggering the workflow, any new content merged to `next` will also become part of the release pull request. You can't assume the same content with fixes will be seen, as new content may have been merged in since the pull request was frozen. -> ** Warning ** -> When re-triggering the workflow, any new content merged to `next` will also become part of the release pull request. You can't just assume that you'll see the same content again but with fixes, as there could have been merged new content in since you froze the pull request. - -When triggering the workflows, always choose the `next` branch as the base unless you know exactly what you're doing. +When triggering the workflows, always choose the `next` branch as the base, unless you know exactly what you are doing. The workflows can be triggered here: @@ -350,61 +302,54 @@ See [Versioning Scenarios](#versioning-scenarios) for a description of each vers ### 5. Make Manual Changes -It's possible and perfectly valid to push manual changes directly on the release branch when needed. Maybe you need to alter the changelog in a way that can't be done purely automatical, or there's another critical change that is needed for the release to work. Any change you make will eventually be merged to `next|main` when the release has been published. +It's possible and valid to push manual changes directly on the release branch when needed. This could be to alter the changelog in a way that can't be done automatically, or another critical change is needed for the release to work. Any changes made will be merged to `next|main` once the release has been published. -This can be used as a quick and dirty way to fix a changelog without needing to change pull requests and waiting for workflows to finish. However it is recommended that you try to use the automated process as much as possible for this, to ensure that the information in GitHub is the single source of truth, and that pull requests and changelogs are in sync. +It's recommended to use the automated process as much as possible to ensure that the information in GitHub is the single source of truth, and that pull requests and changelogs are in sync. ### 6. Merge -When you froze the pull request you also triggered a CI run on the branch. If it's green it's time to merge the pull request. - -If CI is failing for some reason consult with the rest of the core team. These release pull requests are almost exact copies of `next|main` so CI should only fail if they fail too. +When the pull request was frozen, a CI run was triggered on the branch. If it's green, it's time to merge the pull request. If CI is failing for some reason, consult with the rest of the core team. These release pull requests are almost exact copies of `next|main` so CI should only fail if they fail too. ### 7. See the "Publish" Workflow Finish -Merging the pull request will trigger [the publish workflow](https://github.com/storybookjs/storybook/actions/workflows/publish.yml), which does the final publishing. As a Releaser you're responsible for this to finish succesfully, so you should watch it till the end. -If it fails it will notify in Discord, so you can monitor that instead if you want to. +Merging the pull request will trigger [the publish workflow](https://github.com/storybookjs/storybook/actions/workflows/publish.yml), which does the final publishing. As a Releaser, you're responsible for this to finish successfully, so you should watch it until the end. If it fails, it will notify in Discord, so you can monitor that instead if you want to. Done! 🚀 -## Releasing Locally in Case of Emergency 🚨 - -Things fail. Code breaks. Bugs exists. +## Releasing Locally in an Emergency 🚨 -Sometimes we need an emergency escape hatch to release new fixes, even if the automation is broken. +Things can fail, code can break, and bugs can exist. When automation is broken, there may be a need for an emergency escape hatch to release new fixes. In such a situation, it's valid to run the whole release process locally instead of relying on pull requests and workflows. You don't need to create pull requests or split preparation and publishing; you can do it all at once, but make sure you still follow the correct branching strategy. -In those situations it's perfectly valid to run the whole release process locally instead of relying on the pull requests and workflows as usual. When doing this you don't need to create the pull requests either, or split preparation and publishing, you're free to do it all at the same time, but you need to make sure that you follow the correct branching strategy still. +You need a token to the npm registry to publish (set as `YARN_NPM_AUTH_TOKEN`). Currently, @shilman and @ndelangen hold the keys to that castle. -You need a token to the npm registry to be able to publish (set as `YARN_NPM_AUTH_TOKEN` below). Currently @shilman and @ndelangen holds the keys to that castle. - -You can always inspect the workflows to see exactly what they are running and copy that, but below is a general sequence of steps you can take to mimic the automated workflow. Feel free to diverge from this however you need to succeed. +You can inspect the workflows to see what they are running and copy that, but here is a general sequence of steps to mimic the automated workflow. Feel free to deviate from this as needed. 1. Create a new branch from either `next` (prereleases) or `main` (patches) 2. Get all tags: `git fetch --tags origin` 3. `cd scripts` -4. (if patch release) Cherry pick: +4. (If patch release) Cherry pick: 1. `yarn release:pick-patches` - 2. manually cherry pick any patches necessary based on the previous output + 2. Manually cherry pick any necessary patches based on the previous output 5. Bump versions: `yarn release:version --verbose --release-type --pre-id ` -6. To see a list of changes (for your own todo list), run `yarn release:generate-pr-description --current-version --next-version --verbose` +6. To see a list of changes (for your own to-do list), run `yarn release:generate-pr-description --current-version --next-version --verbose` 7. Write changelogs: `yarn release:write-changelog --verbose` -8. `git add .` +8. `git add .`. 9. Commit changes: `git commit -m "Bump version from to MANUALLY"` 10. Merge changes to the release branch: 1. `git checkout <"latest-release" | "next-release">` 2. `git merge ` 3. `git push origin` -11. (if automatic publishing is still working it should kick in now and the rest of the steps can be skipped) +11. (If automatic publishing is still working, it should kick in now and the rest of the steps can be skipped) 12. `cd ..` 13. Install dependencies: `yarn task --task=install --start-from=install` 14. Publish to the registry: `YARN_NPM_AUTH_TOKEN= yarn release:publish --tag <"next" OR "latest"> --verbose` -15. (if patch release) `yarn release:label-patches` -16. [Manually create a GitHub Release](https://github.com/storybookjs/storybook/releases/new) with a tag that is the new version and the target being `latest-release` or `next-release`. +15. (If patch release) `yarn release:label-patches` +16. Manually create a GitHub Release with a tag that is the new version and the target being `latest-release` or `next-release`. 17. Merge to core branch: 1. `git checkout <"next"|"main">` 2. `git merge <"next-release"|"latest-release">` 3. `git push origin` -18. (if patch release) sync `CHANGELOG.md` to `next` with: +18. (If patch release) Sync `CHANGELOG.md` to `next` with: 1. `git checkout next` 2. `git pull` 3. `git checkout origin/main ./CHANGELOG.md` @@ -418,7 +363,7 @@ Not implemented yet. Still work in progress, stay tuned. ## Versioning Scenarios -There are seven types of releases that are done somewhat differently, but following the overall same principles as described previously. +There are seven types of releases that use the same principles, but are done somewhat differently. ### Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13` @@ -433,7 +378,7 @@ This is the default strategy for prereleases, there's nothing special needed to To promote a prerelease to a new prerelease ID, during the [Re-trigger the Workflow](#4-re-trigger-the-workflow) step, choose: - Release type: Prerelease -- Prerelease ID: The ID to promote to. Eg. for alpha -> beta, write "beta" +- Prerelease ID: The ID to promote to. For example, for alpha to beta, write "beta". ### Minor/major releases - `7.1.0-rc.2` -> `7.1.0` or `8.0.0-rc.3` -> `8.0.0` @@ -446,13 +391,13 @@ To promote a prerelease to a new prerelease ID, during the [Re-trigger the Workf The "Patch" release type ensures the current prerelease version gets promoted to a stable version without any major/minor/patch bumps. -This scenario is special in that it turns the `next` branch into a stable branch (until the next prerelease). Therefore this will also force push `next` to `main`, to ensure that `main` contains the latest stable release. Consequently, the history for `main` is lost. +This scenario is special as it turns the `next` branch into a stable branch (until the next prerelease). Therefore, this will also force push `next` to `main`, to ensure that `main` contains the latest stable release. Consequently, the history for `main` is lost. ### First prerelease of new major/minor - `7.1.0` -> `7.2.0-alpha.0` or `8.0.0-alpha.0` **Cadence: Once every quarter** -This is the first prerelease after a stable major/minor has been released. In this case the default versioning strategy for prereleases won't work, because it will do `7.1.0` -> `7.1.1-0`. You need to use the workflow inputs to bump the major/minor correctly: +This is the first prerelease after a stable major/minor has been released. The default versioning strategy for prereleases won't work here, because it will do `7.1.0` -> `7.1.1-0`. You need to use the workflow inputs to bump the major/minor correctly: - Release type: Premajor for `8.0.0-alpha.0` or Preminor for `7.2.0-alpha.0` - Prerelease ID: "alpha" @@ -461,92 +406,78 @@ This is the first prerelease after a stable major/minor has been released. In th **Cadence: Every second week** -This is the default patch release scenario, that cherry picks patches to `main`. +This is the default patch release scenario, which cherry picks patches to `main`. ### Patch releases to earlier versions - subset of `7.1.0-alpha.13` -> `6.5.14` **Cadence: 2-3 times a year** -This happens so rarely on a case by case basis, so this is a completely manual process that isn't accounted for in the automation. The Releaser will find the git tag that matches the patch to bump, eg. `v6.5.14`, check it out, make the necessary changes and follow [the manual release process](#releasing-locally-in-case-of-emergency-🚨). +This happens so rarely on a case by case basis, so this is a completely manual process. The Releaser will find the git tag that matches the patch to bump, eg. `v6.5.14`, check it out, make the necessary changes and follow [the manual release process](#releasing-locally-in-case-of-emergency-🚨). ### Prerelease of upcoming patch release - `7.0.20` -> `7.0.21-alpha.0` **Cadence: Very rare** -In some cases a patch change is so big and complex that it makes sense to first release it as a prerelease of the current patch stable version to see if it works, before releasing it to stable shortly thereafter. +In some cases, a patch change is so big and complex that it makes sense to first release it as a prerelease of the current patch stable version to see if it works, before releasing it to stable shortly thereafter. -There is no process defined for this. +No process is defined for this. ## FAQ ### How do I make changes to the release tooling/process? -The whole process is based on [GitHub Action workflows](../.github/workflows/) and [scripts](../scripts/release/), so it's possible to change them as long as you know what you're doing. +The whole process is based on [GitHub Action workflows](../.github/workflows/) and [scripts](../scripts/release/), so you can modify them if you know what you're doing. The short answer to "how", is to make changes as a regular pull request that is also patched back to `main`. There's a longer answer too, but it's pretty confusing: -Depending on which workflow runs, the scripts are either being runned from `main` or `next`, so if you're making changes to a release script, you want that change in `main` as well for it to have an affect on patch releases. Patching the change back during the normal workflow will mean that the first patch release _will not_ use your change, since it runs before the change has been patched. -If you need it to run as part of the very next patch workflow, you need to manually cherry pick you change to `main`, so that it gets used by the automation immediately. +The scripts run from either `main` or `next`, so if you're changing a release script, you must patch it back to `main` for it to have an effect on patch releases. If you need the change to take effect immediately, you must manually cherry pick it to `main`. -The case isn't the same for changes to workflow files, as they almost always run from `next`, so you don't need to patch them back, but it doesn't hurt. **We recommend always patching any changes back anyway, to be consistent**. The "publish" workflow _does_ run from `latest-release` and `next-release`, so you want changes to _that_ to always be patched back. 🙃 +For workflow file changes, they usually run from `next`, but patching them back is recommended for consistency. The "publish" workflow runs from `latest-release` and `next-release`, so you should always patch changes back for _that_. 🙃 ### Why do I need to re-trigger workflows to update the changelog? -Any changes you made to pull requests' titles, labels or even reverts won't be reflected in the release pull request, because: - -A. It's hopefully frozen -B. Even if it isn't, the workflow only triggers on pushes to `next`, it doesn't trigger when pull request meta data is changed - -Therefore if you've made any changes to pull requests, you need to re-trigger the workflow manually to regenerate the changelog and the version bump. +Changes to pull requests' titles, labels or even reverts won't be reflected in the release pull request. This is because the workflow only triggers on pushes to `next`, not when pull request meta data is changed. -You could also just make the changes to the changelog manually, which isn't totally out of the question, but it means that the pull requests and their title/lables are no longer the single source of truth. +Therefore, if you've made any changes to pull requests, you must re-trigger the workflow manually to regenerate the changelog and the version bump. You could also make the changes to the changelog manually, but it means that the pull requests and their title/labels are no longer the single source of truth. ### Which combination of inputs creates the version bump I need? -Each versioning scenario including how to trigger it with inputs is described in [Versioning Scenarios](#versioning-scenarios). - -You can also see [the tests for the versioning script](https://github.com/storybookjs/storybook/blob/next/scripts/release/__tests__/version.test.ts#L137-L161) to see which inputs creates which outputs. +Each versioning scenario including how to trigger it with inputs is described in [Versioning Scenarios](#versioning-scenarios). You can also see [the tests for the versioning script](https://github.com/storybookjs/storybook/blob/next/scripts/release/__tests__/version.test.ts#L137-L161) to determine which inputs create which outputs. ### Which changes are considered "releasable", and what does it mean? -A specific set of labels define which kind of change a pull request is, and if it is a "releasable" change or not. - -Releasable changes will appear in the changelog and will trigger version bumps, while unreleasable changes will not. - -The exact list of labels and their type is written [here](https://github.com/storybookjs/storybook/blob/next/scripts/release/utils/get-changes.ts#L9-L21). +A specific set of labels define which kind of change a pull request is, and whether it is a "releasable" change or not. Releasable changes will appear in the changelog and will trigger version bumps, while unreleasable changes will not. -Currently releasable labels are: +The exact list of labels and their type is written [here](https://github.com/storybookjs/storybook/blob/next/scripts/release/utils/get-changes.ts#L9-L21). Currently, releasable labels are: - BREAKING CHANGE -- feature request -- bug -- maintenance -- dependencies +- Feature request +- Bug +- Maintenance +- Dependencies And unreleasable labels are: -- documentation -- build +- Documentation +- Build -If a pull request doesn't have any of the above labels at the time of release, it is considered an unreleasable change. - -unreleasable changes are changes that don't affect the user through releases. Documentation-only changes are unreleasable, because they're not part of packages and they don't change behavior. Similarly "build" changes are only internal-facing and doesn't change behavior. This could be tests, CI, etc. +If a pull request does not have any of the above labels at the time of release, it is considered an unreleasable change. Unreleasable changes are changes that do not affect the user through releases. Documentation-only changes are unreleasable, because they are not part of packages and do not change behavior. Similarly, "build" changes are only internal-facing and do not change behavior. This could be tests, CI, etc. ### Why are no release PRs being prepared? -This most likely happens because `next` only contains [unreleasable changes](#which-changes-are-considered-releasable-and-what-does-it-mean), which causes the preparation workflow to cancel itself. That's because it doesn't make sense to prepare a new release if all the changes are unreleasable, as that wouldn't bump the version nor write a new changelog entry, so "releasing" it would just merge it back to `next` without any differences. +This is most likely because `next` only contains [unreleasable changes](#which-changes-are-considered-releasable-and-what-does-it-mean), which causes the preparation workflow to cancel itself. That's because it doesn't make sense to prepare a new release if all the changes are unreleasable, as that wouldn't bump the version nor write a new changelog entry, so "releasing" it would just merge it back to `next` without any differences. -You can always see the workflows and if they've been cancelled [here for prereleases](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and [here for patch releases](https://github.com/storybookjs/storybook/actions/workflows/prepare-patch-release.yml) +You can always see the workflows and if they have been cancelled [here for prereleases](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and [here for patch releases](https://github.com/storybookjs/storybook/actions/workflows/prepare-patch-release.yml). ### Why do we need separate release branches? -A simpler branching approach would be to merge the versioning branches back to `main` or `next` instead of `latest-release` or `next-release`, and then trigger the publishing directly on that branch. After all that is what tools like [Changesets](https://github.com/changesets/changesets) does. +A simpler branching approach would be to merge the versioning branches back to `main` or `next` instead of `latest-release` or `next-release`, and then trigger the publishing directly on that branch. That is what tools like [Changesets](https://github.com/changesets/changesets) do. -The problem with that, is that you could end up publishing changes that wasn't part of the prepared pull request, and thus not part of QA nor the changelog. +The problem with that is you could end up publishing changes that were not part of the prepared pull request, and thus not part of QA nor the changelog. -Take the following scenario, where the Releaser is creating a new release with the frozen branch, and another team member merges a new pull request - "some-simultaneous-bugfix - to `next` _during_ the QA steps: +For example, if the Releaser is creating a new release with the frozen branch and another team member merges a new pull request - "some-simultaneous-bugfix - to `next` _during_ the QA steps: ```mermaid %%{init: { 'gitGraph': { 'mainBranchName': 'next' } } }%% @@ -565,12 +496,11 @@ gitGraph checkout next merge some-simultanous-bugfix type: HIGHLIGHT id: "whoops!" merge version-prerelease-from-7.1.0-alpha.28 tag: "v7.1.0-alpha.29" - ``` -When publishing at the last commit with tag `v7.1.0-alpha.29`, it will publish whatever the content is at that point (all the square dots), which includes the "whoops!" commit from merging the bugfix. But the bugfix was never part of the release pull request because it got prepared before the bugfix was merged in. It essentially becomes a hidden change, hidden from the Releaser and the changelog. +When publishing at the last commit with tag `v7.1.0-alpha.29`, it will publish whatever the content is at that point (all the square dots), which includes the "whoops!" commit from merging the bugfix. But the bugfix was never part of the release pull request because it got prepared before the bugfix was merged in. -If we instead publish from `next-release` and then merge to `next`, will see that the bugfix won't be part of the current release, but the next one: +If we instead publish from `next-release` and then merge to `next`, the bugfix won't be part of the current release, but the next one: ```mermaid %%{init: { 'gitGraph': { 'mainBranchName': 'next' } } }%% From 83579bdfb1075f3c78c177e9a9941428f36e01f3 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 22 Jun 2023 02:44:02 +0200 Subject: [PATCH 09/17] fix git graph --- CONTRIBUTING/RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index cfbbb8a28fc5..8579475c8811 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -494,7 +494,7 @@ gitGraph branch version-prerelease-from-7.1.0-alpha.28 commit id: "bump version" checkout next - merge some-simultanous-bugfix type: HIGHLIGHT id: "whoops!" + merge some-simultaneous-bugfix type: HIGHLIGHT id: "whoops!" merge version-prerelease-from-7.1.0-alpha.28 tag: "v7.1.0-alpha.29" ``` From a10b7abab12884c8c4b2e604a66647d78fb045b7 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 22 Jun 2023 13:32:11 +0200 Subject: [PATCH 10/17] mention git clean for local releases --- CONTRIBUTING/RELEASING.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 8579475c8811..10a888a9db6a 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -324,24 +324,26 @@ You need a token to the npm registry to publish (set as `YARN_NPM_AUTH_TOKEN`). You can inspect the workflows to see what they are running and copy that, but here is a general sequence of steps to mimic the automated workflow. Feel free to deviate from this as needed. +Before you start you should make sure that your working tree is clean and the repository is in a clean state by running `git clean -xdf`. + 1. Create a new branch from either `next` (prereleases) or `main` (patches) 2. Get all tags: `git fetch --tags origin` -3. `cd scripts` -4. (If patch release) Cherry pick: +3. Install dependencies: `yarn task --task=install --start-from=install` +4. `cd scripts` +5. (If patch release) Cherry pick: 1. `yarn release:pick-patches` 2. Manually cherry pick any necessary patches based on the previous output -5. Bump versions: `yarn release:version --verbose --release-type --pre-id ` -6. To see a list of changes (for your own to-do list), run `yarn release:generate-pr-description --current-version --next-version --verbose` -7. Write changelogs: `yarn release:write-changelog --verbose` -8. `git add .`. -9. Commit changes: `git commit -m "Bump version from to MANUALLY"` -10. Merge changes to the release branch: +6. Bump versions: `yarn release:version --verbose --release-type --pre-id ` +7. To see a list of changes (for your own to-do list), run `yarn release:generate-pr-description --current-version --next-version --verbose` +8. Write changelogs: `yarn release:write-changelog --verbose` +9. `git add .`. +10. Commit changes: `git commit -m "Bump version from to MANUALLY"` +11. Merge changes to the release branch: 1. `git checkout <"latest-release" | "next-release">` 2. `git merge ` 3. `git push origin` -11. (If automatic publishing is still working, it should kick in now and the rest of the steps can be skipped) -12. `cd ..` -13. Install dependencies: `yarn task --task=install --start-from=install` +12. (If automatic publishing is still working, it should kick in now and the rest of the steps can be skipped) +13. `cd ..` 14. Publish to the registry: `YARN_NPM_AUTH_TOKEN= yarn release:publish --tag <"next" OR "latest"> --verbose` 15. (If patch release) `yarn release:label-patches` 16. Manually create a GitHub Release with a tag that is the new version and the target being `latest-release` or `next-release`. From 88cbccbbd21e80f581ffeb3152ece1cf447d06c5 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 22 Jun 2023 13:45:44 +0200 Subject: [PATCH 11/17] add faq about patch labels. --- CONTRIBUTING/RELEASING.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 10a888a9db6a..102b9ca9c0b8 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -29,7 +29,9 @@ - [Patch releases to stable - subset of `7.1.0-alpha.13` -\> `7.0.14`](#patch-releases-to-stable---subset-of-710-alpha13---7014) - [Patch releases to earlier versions - subset of `7.1.0-alpha.13` -\> `6.5.14`](#patch-releases-to-earlier-versions---subset-of-710-alpha13---6514) - [Prerelease of upcoming patch release - `7.0.20` -\> `7.0.21-alpha.0`](#prerelease-of-upcoming-patch-release---7020---7021-alpha0) + - [Merges to `main` without versioning](#merges-to-main-without-versioning) - [FAQ](#faq) + - [When should I use the "patch" label?](#when-should-i-use-the-patch-label) - [How do I make changes to the release tooling/process?](#how-do-i-make-changes-to-the-release-toolingprocess) - [Why do I need to re-trigger workflows to update the changelog?](#why-do-i-need-to-re-trigger-workflows-to-update-the-changelog) - [Which combination of inputs creates the version bump I need?](#which-combination-of-inputs-creates-the-version-bump-i-need) @@ -365,7 +367,7 @@ Not implemented yet. Still work in progress, stay tuned. ## Versioning Scenarios -There are seven types of releases that use the same principles, but are done somewhat differently. +There are multiple types of releases that use the same principles, but are done somewhat differently. ### Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13` @@ -424,8 +426,22 @@ In some cases, a patch change is so big and complex that it makes sense to first No process is defined for this. +### Merges to `main` without versioning + +As described in more details in [the Patch Releases section](#patch-releases), there are scenarios where you want to patch [unreleasable](#which-changes-are-considered-releasable-and-what-does-it-mean) content back to `main` without bumping versions or publishing a new release. This happens automatically as long as all the unpicked patch pull requests have unreleasable labels. In that case the prepared patch pull request will change form slighty, to just cherry-picking the patches without bumping the versions. + ## FAQ +### When should I use the "patch" label? + +Not all pull requests need to be patched back to the stable release, which is why only those with the **"patch"** label gets that treatment. But how do you decide whether or not a give pull requests should have that label? + +First of all, patches are only for fixes and minor improvements, and not completely new features. A pull request that introduces a new feature shouldn't be patched back to the stable release. + +Second, any destabilizing changes shouldn't be patched back either. Breaking changes are reserved for major releases, but changes can be destabilizing without being strictly breaking, and those shouldn't be patched back either. An example is moving the settings panel in the manager to a completely different place, but with the same functionality. Many wouldn't consider this breaking because no usage will stop working because of this, but it can be considered a destabilizing change because user behavior have to change as a result of this. + +When in doubt ask the core team for their input. + ### How do I make changes to the release tooling/process? The whole process is based on [GitHub Action workflows](../.github/workflows/) and [scripts](../scripts/release/), so you can modify them if you know what you're doing. From 5053e4e37839b2fcac033598b160e2b6b74a277c Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 22 Jun 2023 13:49:39 +0200 Subject: [PATCH 12/17] add note about temporarily disabling patch changes --- CONTRIBUTING/RELEASING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 102b9ca9c0b8..bbe85c859844 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -261,6 +261,8 @@ You need to check that the release contains the correct stuff. The main things t For example, check if it's a breaking change that isn't allowed in a minor prerelease, or if it's a new feature in a patch release. If it's not suitable, revert the pull request and notify the author. +Sometimes when doing a patch release, a pull request can have the "patch" label but you don't want that change to be part of this release. Maybe you're not confident in the change, or you require more input from maintainers before releasing it. In those situations you should remove the "patch" label from the pull request and follow through with the release (make sure to re-trigger the workflow). When the release is done, add the patch label back again, so it will be part of the next release. + 2. Is the pull request title correct? The title of pull requests is added to the user-facing changelogs, so it must be accurate and understandable. It should follow the pattern "[Area]: [Summary]", where [Area] is the part of the repo that has been changed, and the summary is what has changed. From 6f62669982a1ee36dfe5dbd9b9bd3e94185d9806 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 22 Jun 2023 13:51:53 +0200 Subject: [PATCH 13/17] rephrase relevance note --- CONTRIBUTING/RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index bbe85c859844..4cecab92194d 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -1,7 +1,7 @@ # Releasing > **Note** -> This document is relevant only for core team members who have permissions to release new versions of Storybook. Regular contributors and maintainers can read it for interest or to suggest changes, but they don't have to. +> This document is relevant only for maintainers that have permissions to release new versions of Storybook. Anyone can read it for interest or to suggest changes, but it's not required knowledge. ## Table of Contents From 12c2e5a3c0e43ea27977d6805e767ffc41021633 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 22 Jun 2023 13:55:19 +0200 Subject: [PATCH 14/17] remove cadences --- CONTRIBUTING/RELEASING.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 4cecab92194d..14993d504392 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -373,14 +373,10 @@ There are multiple types of releases that use the same principles, but are done ### Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13` -**Cadence: Multiple times a week** - This is the default strategy for prereleases, there's nothing special needed to trigger this scenario. ### Prerelease promotions - `7.1.0-alpha.13` -> `7.1.0-beta.0` -**Cadence: Once every 1-2 months** - To promote a prerelease to a new prerelease ID, during the [Re-trigger the Workflow](#4-re-trigger-the-workflow) step, choose: - Release type: Prerelease @@ -388,8 +384,6 @@ To promote a prerelease to a new prerelease ID, during the [Re-trigger the Workf ### Minor/major releases - `7.1.0-rc.2` -> `7.1.0` or `8.0.0-rc.3` -> `8.0.0` -**Cadence: Once every quarter** - To promote a prerelease to a new prerelease ID, during the [Re-trigger the Workflow](#4-re-trigger-the-workflow) step, choose: - Release type: Patch @@ -401,8 +395,6 @@ This scenario is special as it turns the `next` branch into a stable branch (unt ### First prerelease of new major/minor - `7.1.0` -> `7.2.0-alpha.0` or `8.0.0-alpha.0` -**Cadence: Once every quarter** - This is the first prerelease after a stable major/minor has been released. The default versioning strategy for prereleases won't work here, because it will do `7.1.0` -> `7.1.1-0`. You need to use the workflow inputs to bump the major/minor correctly: - Release type: Premajor for `8.0.0-alpha.0` or Preminor for `7.2.0-alpha.0` @@ -410,20 +402,14 @@ This is the first prerelease after a stable major/minor has been released. The d ### Patch releases to stable - subset of `7.1.0-alpha.13` -> `7.0.14` -**Cadence: Every second week** - This is the default patch release scenario, which cherry picks patches to `main`. ### Patch releases to earlier versions - subset of `7.1.0-alpha.13` -> `6.5.14` -**Cadence: 2-3 times a year** - This happens so rarely on a case by case basis, so this is a completely manual process. The Releaser will find the git tag that matches the patch to bump, eg. `v6.5.14`, check it out, make the necessary changes and follow [the manual release process](#releasing-locally-in-case-of-emergency-🚨). ### Prerelease of upcoming patch release - `7.0.20` -> `7.0.21-alpha.0` -**Cadence: Very rare** - In some cases, a patch change is so big and complex that it makes sense to first release it as a prerelease of the current patch stable version to see if it works, before releasing it to stable shortly thereafter. No process is defined for this. From 935e37b4817d23f06953ccfa59a19e73d30a8e06 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 22 Jun 2023 13:56:46 +0200 Subject: [PATCH 15/17] Apply suggestions from @vanessayuenn Co-authored-by: Vanessa Yuen <6842965+vanessayuenn@users.noreply.github.com> --- CONTRIBUTING/RELEASING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index bbe85c859844..b6f5951ac897 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -46,9 +46,9 @@ This document explains the release process for the Storybook monorepo. There are 1. Prereleases and major/minor releases - releasing any content that is on the `next` branch 2. Patch releases - picking any content from `next` to `main`, that needs to be patched back to the current stable minor release -The release process is based on automatically created "Release Pull Requests", that when merged will trigger a new version to be released. A designated Releaser - can be a different team member from time to time - will go through the release process in the current Release PR. +The release process is based on automatically created "Release Pull Requests", that when merged will trigger a new version to be released. -A designated Releaser - which may change - will go through the release process in the current Release PR. This process is implemented with NodeJS scripts in [`scripts/release`](../scripts/release/) and three GitHub Actions workflows: +A designated Releaser -- which may rotate between core team members -- will go through the release process in the current Release PR. This process is implemented with NodeJS scripts in [`scripts/release`](../scripts/release/) and three GitHub Actions workflows: - [Prepare Prerelease PR](../.github/workflows/prepare-prerelease.yml) - [Prepare Patch PR](../.github/workflows/prepare-patch-release.yml) From c879e6c151fd6170606ad3c4a4a888efdb0a1aab Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Fri, 23 Jun 2023 09:36:14 +0200 Subject: [PATCH 16/17] re-phrasing --- CONTRIBUTING/RELEASING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 7cc5a5f41188..120d834f839a 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -255,7 +255,7 @@ The "**freeze**" label does not cancel the workflows when they are triggered man ### 3. QA Each Merged Pull Request -You need to check that the release contains the correct stuff. The main things to check for are: +It is important to verify that the release includes the right content. Key elements to account for are: 1. Is the change suitable for the version bump? @@ -324,7 +324,7 @@ Done! 🚀 Things can fail, code can break, and bugs can exist. When automation is broken, there may be a need for an emergency escape hatch to release new fixes. In such a situation, it's valid to run the whole release process locally instead of relying on pull requests and workflows. You don't need to create pull requests or split preparation and publishing; you can do it all at once, but make sure you still follow the correct branching strategy. -You need a token to the npm registry to publish (set as `YARN_NPM_AUTH_TOKEN`). Currently, @shilman and @ndelangen hold the keys to that castle. +You need a token to the npm registry to publish (set as `YARN_NPM_AUTH_TOKEN`), which you can get from @shilman or @ndelangen. You can inspect the workflows to see what they are running and copy that, but here is a general sequence of steps to mimic the automated workflow. Feel free to deviate from this as needed. From d1912cdef20b8817b66f865f8b514afad015c32c Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 27 Jun 2023 01:32:15 +0200 Subject: [PATCH 17/17] add note about starting CI, see #23198 --- CONTRIBUTING/RELEASING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 120d834f839a..5a999af5e6de 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -13,7 +13,7 @@ - [Publishing](#publishing) - [👉 How to Release](#-how-to-release) - [1. Find the Prepared Pull Request](#1-find-the-prepared-pull-request) - - [2. Freeze the Pull Request](#2-freeze-the-pull-request) + - [2. Freeze the Pull Request and run CI](#2-freeze-the-pull-request-and-run-ci) - [3. QA Each Merged Pull Request](#3-qa-each-merged-pull-request) - [4. Re-trigger the Workflow](#4-re-trigger-the-workflow) - [5. Make Manual Changes](#5-make-manual-changes) @@ -247,12 +247,14 @@ Look for the release pull request that has been prepared for the type of release For example: https://github.com/storybookjs/storybook/pull/23148 -### 2. Freeze the Pull Request +### 2. Freeze the Pull Request and run CI Add the "**freeze**" label to the pull request. This will stop the preparation workflows from running when new changes to `next` are merged. This allows you to make changes without worrying about other people's work overriding yours. The "**freeze**" label does not cancel the workflows when they are triggered manually, so you can still run the workflow. +You also need to add the "**ci:daily**" label to the pull request to trigger CI runs. This will start a full CI run and re-run on any changes. CI does not run by default to avoid unnecessary re-runs until a new release is being created. + ### 3. QA Each Merged Pull Request It is important to verify that the release includes the right content. Key elements to account for are: