Skip to content

Instantly share code, notes, and snippets.

@pvdlg
Last active January 18, 2024 09:18
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pvdlg/9fa33dbc6792edc2cf3d0765c5d8b8b3 to your computer and use it in GitHub Desktop.
Save pvdlg/9fa33dbc6792edc2cf3d0765c5d8b8b3 to your computer and use it in GitHub Desktop.

Release workflows

  1. Branch types
    1. Single release branch
      1. Single release branch workflows
      2. Single release branch configuration
    2. Non release branch
      1. Non release branch Workflow
      2. Non release branch configuration
    3. Future branches
      1. Future branches workflow
      2. Future branches configuration
    4. LTS branches
      1. LTS branches workflow
      2. LTS branches configuration
    5. Pre-releases
      1. Pre-releases workflow
      2. Pre-releases configuration
  2. Configuration
    1. Format
    2. Branches naming convention
    3. Configuration validation

// TODO handle case where a release already exists on a higher branch

Terminology:

Term Definition
Default branch Git branch that contains the stable code (made available to all users). Usually named master.
Future branches Permanent Git branch that contains the unstable code (made available only to early adopters). Usually named next.
Channel Distribution channels used to limit the accessibility of a release to a subset of users. For example dist-tags in npm.
Default channel Distribution channel to make stable version available to every users. Usually named latest.
Future channels Distribution channels to make unstable version available to early adopters. Usually named next or unstable.
Pre-release version version that doesn't follow the regular semver increment pattern. For example any increment of 2.0.0-beta.1 is 2.0.0-beta.2, then 2.0.0-beta.2 etc...
Version version following the semver rules. For example, a minor increment of 1.0.0 is 1.1.0.
Module A software meant to be used as a dependency of other software. It's not meant to be used by final users.
Application A software meant to be used by final users. No other software Dependents on it.
Module version The version of a module which is meant for machine and not for humans. For example a major release indicate a breaking change but doesn't cary any marketing or emotional value.
Application version The version of an application which is meant for humans. For example a major release carry has a marketing meaning.

Branch types

Single release branch

Simple workflow publishing release for each commits on the master branch.

Ideal for small or stable modules receiving few breaking changes.

Branch: master

Channel: latest if supported by the release target

Single release branch workflows

Action Result
push to master Release on default channel (if supported, otherwise no channel)
merge feature branch to master Release on default channel (if supported, otherwise no channel)

Single release branch configuration

"release": {
  "branches": ["master"]
}

Non release branch

Simple workflow publishing a release when merging dev into master branch. Pushing commits to dev does not trigger a release.

Ideal for modules in early stage development or during a development period with frequent breaking changes that doesn't need to be made available to users right away. This workflow allow to limit the release frequency by grouping multiple commits in one release.

Branch: master, dev

Channel: latest if supported by the release target

Non release branch Workflow

Action Result
push to master Release on default channel (if supported, otherwise no channel)
merge feature branch to master Release on default channel (if supported, otherwise no channel)
push to dev -
merge feature branch to dev -
merge dev branch to master Release on default channel (if supported, otherwise no channel)

Non release branch configuration

"release": {
  "branches": ["master"]
}

Future branches

Channel based workflow to release on different channels. Make future release available on latest channel when merging associated branches into master branch.

Ideal for stable modules, distributing new features to a subset of users as soon as possible. This workflow allow to to get feedback from early adopters while limiting the risk of regression for other users.

Branch: master, next

Channel: latest, next

Future branches workflow

Action Result
push to master Release on latest channel if the commit trigger a release with a version that doesn't satisfies the type defines for next branch, report an error otherwise
merge feature branch to master Release on latest channel if the commit trigger a release with a version that doesn't satisfies the type defines for next branch, report an error otherwise
push to next Release on next channel
merge any branch to next Release on next channel
merge some commits from next branch to master Get the last release for commits on branch master (including the one that come from next), analyze commits, increase the version; If the commit associated with the last release found is also present on next, make the version available on the latest channel, otherwise do a regular release.

Future branches configuration

"release": {
  "branches": ["master", {
		"branch": "next",
		"type": "major"
		}]
}

The type is required for future branches and indicate that its reserved for a certain type of release (minor or major). That defines how much ahead the future branch must be versus the pervious branch in the list. If configured with major, the next branch must always one major release ahead of master.

Example 1:

  • if the last release on latest is 2.1.0 and next is configured with "type" : "major"
  • and the last release on next is also 2.1.0
  • then any releases are allowed on latest
  • And only major releases are allowed on next

Example 2:

  • if the last release on latest is 2.1.0 and next is configured with "type" : "major"
  • and the last release on next is 2.5.0
  • then patch releases are allowed on latest, but minor and major are forbidden
  • And any releases are allowed on next

Example 3:

  • if the last release on latest is 2.1.0 and next is configured with "type" : "major"
  • and the last release on next is 3.0.0
  • then patch and minor releases are allowed on latest, but major are forbidden
  • And any releases are allowed on next

Example 4:

  • if the last release on latest is 3.0.0 and next is configured with "type" : "major"
  • and the last release on next is 2.5.0
  • And any releases are allowed on latest
  • No releases are allowed on next (it's considered stalled) until master is merged into next. Once it's done the situation becomes the one described in Example 1

That enforces consistence across versions: if a x.y.z version exists on a given channel, all superior versions on that channel must includes all the commits of x.y.z

If not specified the type is major.

With specific channel names:

"release": {
  "branches": ["master", {
    "branch": "next",
    "channel": "experimental",
		"type": "major"
  }]
}

LTS branches

Workflow to release legacy versions.

Ideal for modules maintaining legacy versions and doing bug fixes and features only releases.

Branch: 1.x.x, 2.x.x, master (any number of lts branches are supported)

Channel: latest if supported by the release target

LTS branches workflow

Action Result
push to master Release on default channel (if supported, otherwise no channel)
merge feature branch to master Release on default channel (if supported, otherwise no channel)
push to next -
push to 1.x.x or 2.x.x branches Release on default channel (if supported, otherwise no channel) if in range, report an error and error otherwise
merge any branch to 1.x.x or 2.x.x branches Release on default channel (if supported, otherwise no channel) if in range, report an error and error otherwise

Note: By default lts releases are published on the default channel as they are limited to a range of versions inferior to the versions released from master. Dependents will get only the expected releases by specifying a range dependency (for example ^2.0.0). If a channel is specified, then the release will be made on this channel.

LTS branches configuration

"release": {
  "branches": ["1.x.x", "2.x.x", "master"]
  }
}

With specific channel names:

"release": {
  "branches": [{
    "branch": "v1",
    "range": "1.x.x",
    "channel": "v1"
  }, {
    "branch": "v2",
    "range": "2.x.x",
    "channel": "v2"
  }, "master"]
}

Pre-releases

Workflow to distribute releases without incrementing the semantic version during the development of new a application version.
Ideal for applications distributing unstable/alpha/preview releases.

Branch: master, next-beta, next-alpha

Channel: latest if supported by the release target

Pre-releases workflow

Action Result
push to master Release on default channel (if supported, otherwise no channel)
merge 4.0.0-beta, 5.0.0-beta or any branch to master Release on default channel (if supported, otherwise no channel)
push to 4.0.0-beta Release a prerelease version (4.0.0-beta, then 4.0.0-beta.1, then 4.0.0-beta.2) on default channel (if supported, otherwise no channel)
merge any branch to 4.0.0-beta Release a prerelease version (4.0.0-beta, then 4.0.0-beta.1, then 4.0.0-beta.2) on default channel (if supported, otherwise no channel)
push to 5.0.0-beta Release a prerelease version (5.0.0-beta, then 5.0.0-beta.1, then 5.0.0-beta.2) on default channel (if supported, otherwise no channel)
merge any branch to 5.0.0-beta Release a prerelease version (5.0.0-beta, then 5.0.0-beta.1, then 5.0.0-beta.2) on default channel (if supported, otherwise no channel)

Pre-releases configuration

"release": {
  "branches": ["master", "4.0.0-beta", "5.0.0-alpha"]
}

With specific branch names:

"release": {
  "branches": ["master", {
    "branch": "dev",
    "prerelease": "4.0.0-alpha"
  }, {
    "branch": "experimental",
    "prerelease": "5.0.0-beta"
  }]
}

Configuration

Format

Each branch is either a String or an Object with the following properties:

Option Description
branch Require Git branch name
range Accepted version range for to release from this branch. If defined the branch will be considered a LTS branch.
channel Channel on which to release. Ignored for release targets that doesn't support channels.
prerelease Version of the pre-release done from this branch. Must be formatted <version>-<tag>. The version is required, the tag is optional. Examples: 4.0.0-beta, 4.0.0.

The order in which the branches are defines is meaningful, and it is used to determine the branch type and which release can be done from each branch.

Branches naming convention

In a branch is defined as a String semantic-release will automatically determine the type of branch as follow:

  • If the branch name is a valid semver range (1.x.x, 1.0.x, ^1.0.0) the branch will be considered a LTS branch, with its range based on the branch name, and release on the channel with the same name formatted 1.x.x (^1.0.0 => 1.x.x)
  • The first branch in the branches Array after the LTS branches is considered the default branch (usually master).
  • Each branches in the branches Array after the default branch are considered future branches.
  • If the branch name is a valid semver version (4.0.0) or formatted like <version>-<tag> the branch will be considered a pre-release branch with version and tag based on the branch name.

For example:

"release": {
  "branches": ["1.x.x", "master", "next", "5.0.0-alpha"]
}

is equivalent of:

"release": {
  "branches": [{
    "branch": "1.x.x",
    "range": "1.x.x",
    "channel": "latest"
  }, {
    "branch": "master",
    "channel": "latest"
  }, {
    "branch": "next",
    "channel": "next"
  }, {
    "branch": "5.0.0-alpha",
    "prerelease": "5.0.0-beta"
  }]
}

Configuration validation

The branches configuration must follow these rules:

  • A default branch is required.
  • LTS branches range must not overlap. For example ["1.x.x", 1.5.x] is invalid.
  • LTS branches must be defined before the default branch.
  • Futures branches must be defined after the default branch.
  • Default and future branches cannot define a range.
@felixfbecker
Copy link

I would like to point out that prereleases are part of the semver spec, so a prerelease version or a package using prereleases is just as much "following semver rules" as one that doesn't.

The type is required for future branches and indicate that its reserved for a certain type of release (minor or major). That defines how much ahead the future branch must be versus the pervious branch in the list. If configured with major, the next branch must always one major release ahead of master.

I don't understand why it is so important to enforce that next most be a major or any version ahead of latest. The only requirement I would want to do is ensure that next is always >= latest. Meaning, I can publish a feature on next too, because that may introduce bugs. And then once it was tested enough, I can merge that into master/latest, and next will be == latest, until the next release on next is done.

LTS branches

push to master Release on default channel

What does "release on default channel" mean here? If latest is 3.2.1, and I am releasing 2.8 on 2.x.x, then obviously I don't want to update latest to point to 3.2.1?

Prereleases

push to 4.0.0-beta | Release a prerelease version (4.0.0-beta, then 4.0.0-beta.1, then 4.0.0-beta.2) on default channel (if supported, otherwise no channel)

What does "release on default channel" mean here? The most important thing here imo would be to make sure latest does not get updated when releasing a prerelease version, but stays on the latest stable version from master.

Is there a reason why the branch has to be named like 4.0.0-beta and not just beta?

prerelease | Version of the pre-release done from this branch. Must be formatted -. The version is required, the tag is optional. Examples: 4.0.0-beta, 4.0.0.

Does this mean I have to change the package.json release config everytime I want to publish a new prerelease?

Branches naming convention

If the branch name is a valid semver range (1.x.x, 1.0.x, ^1.0.0) the branch will be considered a LTS branch, with its range based on the branch name, and release on the channel with the same name formatted 1.x.x (^1.0.0 => 1.x.x)

Note that branch names cannot contain any ASCII control characters like "~", "^", ":", so ^1.0.0 would not be valid.
https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html

LTS branches range must not overlap. For example ["1.x.x", 1.5.x] is invalid.

How would I have an LTS branch for >=1.0.0<1.5.0 then? Using </> in branch names could get very annoying because they are reserved characters in shell for redirection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment