Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/create-release-proposal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ on:
required: true
type: string
description: The release date in YYYY-MM-DD format
release-version:
required: false
type: string
description: 'The version to release, if it cannot be inferred. e.g: 27.0.0-alpha.0.0.0'

concurrency: ${{ github.workflow }}

Expand All @@ -31,6 +35,7 @@ jobs:
RELEASE_BRANCH: v${{ inputs.release-line }}.x
RELEASE_DATE: ${{ inputs.release-date }}
RELEASE_LINE: ${{ inputs.release-line }}
RELEASE_VERSION: ${{ inputs.release-version }}
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -74,7 +79,7 @@ jobs:
# The curl command is to make sure we run the version of the script corresponding to the current workflow.
run: |
curl -fsSL https://github.com/${GITHUB_REPOSITORY}/raw/${GITHUB_SHA}/tools/actions/create-release-proposal.sh |\
sh -s -- "${RELEASE_DATE}" "${RELEASE_LINE}" "${GITHUB_ACTOR}"
sh -s -- "${RELEASE_DATE}" "${RELEASE_LINE}" "${GITHUB_ACTOR}" "${RELEASE_VERSION}"
env:
GH_TOKEN: ${{ github.token }}
# We want the bot to push the push the release commit so CI runs on it.
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/lint-release-proposal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- v[0-9]+.[0-9]+.[0-9]+-proposal
- v[0-9]+.0.0-alpha.[0-9]+.[0-9]+.[0-9]+-proposal

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
Expand Down Expand Up @@ -34,8 +35,11 @@ jobs:
$2 == "NODE_MAJOR_VERSION" { maj = $3 }
$2 == "NODE_MINOR_VERSION" { min = $3 }
$2 == "NODE_PATCH_VERSION" { pat = $3 }
$2 == "NODE_ALPHA_MAJOR_VERSION" { amaj = $3 }
$2 == "NODE_ALPHA_MINOR_VERSION" { amin = $3 }
$2 == "NODE_ALPHA_PATCH_VERSION" { apat = $3 }
$2 == "NODE_VERSION_LTS_CODENAME" { gsub(/^"|"$/, "'"'"'",$3); lts = $3 }
END { if (maj) print maj "\\." min "\\." pat " " (lts != "'"''"'" ? lts " \\(LTS" : "\\(Current" ) "\\)" }
END { if (maj) print maj "\\." min "\\." pat (amaj != "" ? "-alpha\\." amaj "\\." amin "\\." apat " \\(Alpha" : " " (lts != "'"''"'" ? lts " \\(LTS" : "\\(Current" )) "\\)" }
')
MAJOR=${VERSION%%\\.*}
EXPECTED_TITLE='^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}, Version '"$VERSION"'$'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/major-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
MONTH=$(date +'%m')
DAY=$(date +'%d')
# We'll create the reminder issue two months prior the release
if [[ "$MONTH" == "02" || "$MONTH" == "08" ]] && [[ "$DAY" == "15" ]]; then
if [[ "$MONTH" == "02" ]] && [[ "$DAY" == "15" ]]; then
echo "create_issue=true" >> "$GITHUB_ENV"
fi
- name: Retrieve next major release info from nodejs/Release
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,7 @@ ifndef DISTTYPE
DISTTYPE=release
endif
ifeq ($(DISTTYPE),release)
FULLVERSION=$(VERSION)
FULLVERSION=v$(shell $(PYTHON) tools/getnodeversion.py --full)
else # ifeq ($(DISTTYPE),release)
ifeq ($(DISTTYPE),custom)
ifndef CUSTOMTAG
Expand Down
127 changes: 115 additions & 12 deletions doc/contributing/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,9 @@ The new entry should take the following form:
* Include the full list of commits since the last release here. Do not include "Working on X.Y.Z+1" commits.
```

The release type should be either Current, LTS, or Maintenance, depending on the
type of release being produced.
The release type should be either Alpha, Current, or LTS, depending
on the type of release being produced. Release lines before Node.js 27
also have a Maintenance phase.

By default, the `### Notable changes` section should be populated with the
commits in the release that have either the `notable-change` or `semver-minor`
Expand Down Expand Up @@ -903,13 +904,20 @@ git secure-tag <vx.y.z> <commit-sha> -sm "YYYY-MM-DD Node.js vx.y.z (<release-ty

<sup>The commit-sha is the release commit. You can get it easily by running `git rev-parse HEAD`</sup>

`release-type` is either "Current" or "LTS". For LTS releases, you should also
include the release code name.
`release-type` is either "Alpha", "Current" or "LTS". For LTS releases, you
should also include the release code name.

```text
2019-10-22 Node.js v10.17.0 'Dubnium' (LTS) Release
```

For Alpha releases, the tag name includes the full pre-release version (see
[Alpha releases](#alpha-releases-october-to-april)):

```text
2026-10-28 Node.js v27.0.0-alpha.0.0.0 (Alpha) Release
```

The tag **must** be signed using the GPG key that's listed for you on the
project README.

Expand All @@ -930,6 +938,9 @@ Working on vx.y.z # where 'z' is the incremented patch number
PR-URL: <full URL to your release proposal PR>
```

For Alpha releases, use the full pre-release version, e.g.
`Working on v27.0.0-alpha.1.0.1`.

This sets up the branch so that nightly builds are produced with the next
version number _and_ a pre-release tag.

Expand Down Expand Up @@ -1394,6 +1405,13 @@ announced immediately following the release of 12.0.0).

### Branch-off (October)

#### Tracking issue

Open an issue in the [Release repository][] titled `Prepare for vN`, listing
the pull requests needed to prepare the release line (e.g. tooling updates or
the expected assets in the Build repository). Keep it updated as pull requests
are opened and land.

#### Release branch

Approximately six months before a major release, new `vN.x` and
Expand Down Expand Up @@ -1427,10 +1445,25 @@ labels of previous releases.

#### Initial Alpha release proposal

A draft release proposal should be created before the release. A
separate `vN.x-proposal` branch should be created that tracks the `vN.x`
branch. This branch will contain the draft release commit (with the draft
changelog).
A draft release proposal should be created before the release. Once the
`vN.x-staging` branch exists, use the [`create-release-proposal`][] action with
the first Alpha version:

```bash
gh workflow run "Create Release Proposal" -f release-line=N -f release-date=YYYY-MM-DD -f release-version=N.0.0-alpha.0.0.0
```

Or run it locally:

```bash
git node release -S --prepare --newVersion=N.0.0-alpha.0.0.0
```

This creates the `vN.0.0-alpha.0.0.0-proposal` branch with the draft release
commit, which also creates `doc/changelogs/CHANGELOG_V{N}.md`, updates the
navigation headers and the root `CHANGELOG.md`, and generates the changelog
against `vN-1.x`, as described in the sections below. Review those changes
before opening the release proposal.

Notify the `@nodejs/npm` team in the release proposal PR to inform them of the
upcoming release.
Expand Down Expand Up @@ -1510,14 +1543,77 @@ version of the release), in the [expected assets][] folder. The change will
need to be deployed onto the web server by a member of the [build-infra team][]
before the release is promoted.

### Alpha releases (October to April)

Between the branch-off and the semver-major release, the `vN.x` line is
released as Alpha versions. Unlike Current and LTS lines, Alpha releases may
contain `SEMVER-MAJOR` changes.

#### Versioning

Alpha versions use the `vN.0.0-alpha.X.Y.Z` format, where `X.Y.Z` follows
[Semantic Versioning][] relative to the previous Alpha release, so users can
tell the kind of changes between two Alpha releases. The first Alpha release of
a line is `vN.0.0-alpha.0.0.0`.

Set the `NODE_ALPHA_*_VERSION` macros in `src/node_version.h` as follows,
leaving `NODE_MAJOR_VERSION` at `N` and `NODE_MINOR_VERSION` and
`NODE_PATCH_VERSION` at `0`:

* If the release contains at least one `SEMVER-MAJOR` commit, increment
`NODE_ALPHA_MAJOR_VERSION` and reset `NODE_ALPHA_MINOR_VERSION` and
`NODE_ALPHA_PATCH_VERSION` to `0`.
* Otherwise, if it contains at least one `SEMVER-MINOR` commit, increment
`NODE_ALPHA_MINOR_VERSION` and reset `NODE_ALPHA_PATCH_VERSION` to `0`.
* Otherwise, increment `NODE_ALPHA_PATCH_VERSION`.

For example, `v27.0.0-alpha.0.0.0` followed by a release containing a
`SEMVER-MAJOR` commit is `v27.0.0-alpha.1.0.0`. The git tag, the release commit
title and the changelog entries all use this full version:

```text
2026-10-28, Version 27.0.0-alpha.0.0.0 (Alpha)
```

#### Preparing an Alpha release

Alpha releases pick up commits from `main`, including `SEMVER-MAJOR` ones. To
list the commits to land on `vN.x-staging`, do not exclude the `semver-major`
label:

```bash
N=27 sh -c 'branch-diff v$N.x-staging upstream/main --exclude-label=dont-land-on-v$N.x,backport-requested-v$N.x,backport-blocked-v$N.x,backport-open-v$N.x,backported-to-v$N.x --filter-release --format=simple'
```

The [`create-release-proposal`][] action and `git node release --prepare`
compute the next Alpha version from the commit labels. The proposal branch is
named after the full version, e.g. `v27.0.0-alpha.1.0.0-proposal`.

Alpha releases are added to `doc/changelogs/CHANGELOG_V{N}.md`, in the same way
as Current releases, and are labeled "Alpha" in the changelog tables. List all
`SEMVER-MAJOR` commits that are not strictly internal, test, or doc-related as
notable changes.

Alpha releases are published on GitHub as pre-releases, so they are not marked
as the latest release.

`REPLACEME` placeholders are replaced with the full Alpha version (e.g.
`v27.0.0-alpha.1.0.0`).

### Semver-major release (April)

#### Release proposal

A draft release proposal should be created 6 weeks before the release. A
separate `vN.x-proposal` branch should be created that tracks the `vN.x`
branch. This branch will contain the draft release commit (with the draft
changelog).
A draft release proposal should be created 2 weeks before the release, using
the [`create-release-proposal`][] action with `-f release-version=N.0.0`, or
locally:

```bash
git node release -S --prepare --newVersion=N.0.0
```

This creates the `vN.0.0-proposal` branch with the draft release commit (with
the draft changelog).

Notify the `@nodejs/npm` team in the release proposal PR to inform them of the
upcoming release.
Expand All @@ -1535,6 +1631,11 @@ To mark a release line as stable, the following changes must be made to
* Remove `NODE_ALPHA_MAJOR_VERSION`, `NODE_ALPHA_MINOR_VERSION`, and
`NODE_ALPHA_PATCH_VERSION`.

The "Alpha" labels in `doc/changelogs/CHANGELOG_V{N}.md` and `CHANGELOG.md`
must be changed to "Current". `git node release --prepare --newVersion=N.0.0`
does both, and `git node release --promote` bumps `NODE_MAJOR_VERSION` on
`main` when cherry-picking the release commit.

#### Generate the notable changes

For a major release, all SEMVER-MAJOR commits that are not strictly internal,
Expand Down Expand Up @@ -1590,6 +1691,8 @@ Typical resolution: sign the release again.

[Build issue tracker]: https://github.com/nodejs/build/issues/new
[Node.js Snap management repository]: https://github.com/nodejs/snap
[Release repository]: https://github.com/nodejs/Release
[Semantic Versioning]: https://semver.org/
[Snap]: https://snapcraft.io/node
[`create-release-post.yml`]: https://github.com/nodejs/nodejs.org/actions/workflows/create-release-post.yml
[`create-release-proposal`]: https://github.com/nodejs/node/actions/workflows/create-release-proposal.yml
Expand Down
19 changes: 14 additions & 5 deletions test/parallel/test-release-changelog.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,16 @@ if (!release) {
const major = getDefine(versionText, 'NODE_MAJOR_VERSION');
const minor = getDefine(versionText, 'NODE_MINOR_VERSION');
const patch = getDefine(versionText, 'NODE_PATCH_VERSION');
const versionForRegex = RegExp.escape(`${major}.${minor}.${patch}`);
let version = `${major}.${minor}.${patch}`;
const alpha = /#define\s+NODE_ALPHA_MAJOR_VERSION\s/.test(versionText);
if (alpha) {
const alphaMajor = getDefine(versionText, 'NODE_ALPHA_MAJOR_VERSION');
const alphaMinor = getDefine(versionText, 'NODE_ALPHA_MINOR_VERSION');
const alphaPatch = getDefine(versionText, 'NODE_ALPHA_PATCH_VERSION');
version += `-alpha.${alphaMajor}.${alphaMinor}.${alphaPatch}`;
}
const versionForRegex = RegExp.escape(version);
const releaseType = alpha ? 'Alpha' : 'Current';

const lts = getDefine(versionText, 'NODE_VERSION_IS_LTS') !== '0';
const codename = getDefine(versionText, 'NODE_VERSION_LTS_CODENAME').slice(1, -1);
Expand All @@ -47,7 +56,7 @@ const changelogPath = `doc/changelogs/CHANGELOG_V${major}.md`;
if (lts) {
tableHeader = new RegExp(`<th>LTS '${RegExp.escape(codename)}'</th>`);
} else {
tableHeader = /<th>Current<\/th>/;
tableHeader = new RegExp(`<th>${releaseType}</th>`);
}
assert.match(changelog, tableHeader);
// Check table contains link to this release.
Expand All @@ -59,7 +68,7 @@ const changelogPath = `doc/changelogs/CHANGELOG_V${major}.md`;
if (lts) {
title = new RegExp(`## \\d{4}-\\d{2}-\\d{2}, Version ${versionForRegex} '${RegExp.escape(codename)}' \\(LTS\\), @\\S+`);
} else {
title = new RegExp(`## \\d{4}-\\d{2}-\\d{2}, Version ${versionForRegex} \\(Current\\), @\\S+`);
title = new RegExp(`## \\d{4}-\\d{2}-\\d{2}, Version ${versionForRegex} \\(${releaseType}\\), @\\S+`);
}
assert.match(changelog, title);
}
Expand All @@ -72,15 +81,15 @@ const changelogPath = `doc/changelogs/CHANGELOG_V${major}.md`;
if (lts) {
linkToChangelog = new RegExp(`\\[Node\\.js ${major}\\]\\(${RegExp.escape(changelogPath)}\\) \\*\\*Long Term Support\\*\\*`);
} else {
linkToChangelog = new RegExp(`\\[Node\\.js ${major}\\]\\(${RegExp.escape(changelogPath)}\\) \\*\\*Current\\*\\*`);
linkToChangelog = new RegExp(`\\[Node\\.js ${major}\\]\\(${RegExp.escape(changelogPath)}\\) \\*\\*${releaseType}\\*\\*`);
}
assert.match(mainChangelog, linkToChangelog);
// Check table header.
let tableHeader;
if (lts) {
tableHeader = new RegExp(`<th title="LTS Until \\d{4}-\\d{2}"><a href="${RegExp.escape(changelogPath)}">${major}</a> \\(LTS\\)</th>`);
} else {
tableHeader = new RegExp(`<th title="Current"><a href="${RegExp.escape(changelogPath)}">${major}</a> \\(Current\\)</th>`);
tableHeader = new RegExp(`<th title="${releaseType}"><a href="${RegExp.escape(changelogPath)}">${major}</a> \\(${releaseType}\\)</th>`);
}
assert.match(mainChangelog, tableHeader);
// Check the table contains a link to the release in the appropriate CHANGELOG_V*.md file.
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-release-npm.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const assert = require('assert');
const child_process = require('child_process');
const path = require('path');

const releaseReg = /^v\d+\.\d+\.\d+$/;
const releaseReg = /^v\d+(?:\.0\.0-alpha\.\d+)?\.\d+\.\d+$/;

// Npm requires crypto support.
if (!releaseReg.test(process.version) || !common.hasCrypto) {
Expand Down
6 changes: 4 additions & 2 deletions tools/actions/create-release-proposal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ BOT_TOKEN=${BOT_TOKEN:-}
RELEASE_DATE=$1
RELEASE_LINE=$2
RELEASER=$3
RELEASE_VERSION=$4

if [ -z "$RELEASE_DATE" ] || [ -z "$RELEASE_LINE" ]; then
echo "Usage: $0 <RELEASE_DATE> <RELEASE_LINE>"
echo "Usage: $0 <RELEASE_DATE> <RELEASE_LINE> [<RELEASER> [<RELEASE_VERSION>]]"
exit 1
fi

Expand All @@ -28,7 +29,8 @@ done

set -xe

git node release --prepare --skipBranchDiff --yes --releaseDate "$RELEASE_DATE"
git node release --prepare --skipBranchDiff --yes --releaseDate "$RELEASE_DATE" \
${RELEASE_VERSION:+--newVersion "$RELEASE_VERSION"}

HEAD_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
HEAD_SHA="$(git rev-parse HEAD^)"
Expand Down
3 changes: 2 additions & 1 deletion tools/actions/lint-release-proposal-commit-list.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ if (commitListingStart === -1) {
} else {
// We can't assume the Commits section is the one for this release in case of
// a release to transition to LTS (i.e. with no commits).
const releaseStart = /\n<a id="(\d+\.\d+\.\d+)"><\/a>\n\n## \d+-\d+-\d+, Version \1/.exec(changelog);
const releaseStart = /\n<a id="(\d+(?:\.0\.0-alpha\.\d+)?\.\d+\.\d+)"><\/a>\n\n## \d+-\d+-\d+, Version \1/.exec(changelog);
assert.ok(releaseStart, 'Could not determine the start of the release section');
const releaseEnd = changelog.indexOf('\n\n<a', releaseStart.index);
assert.notStrictEqual(releaseEnd, -1, 'Could not determine the end of the release section');
Expand All @@ -39,6 +39,7 @@ if (commitListingStart === -1) {

// Normalize for consistent comparison
commitList = commitList
.replaceAll('**(SEMVER-MAJOR)** ', '')
.replaceAll('**(SEMVER-MINOR)** ', '')
.replaceAll(/(?<= - )\*\*\(CVE-\d{4}-\d+\)\*\* (?=\*\*)/g, '')
.replaceAll('\\', '');
Expand Down
24 changes: 23 additions & 1 deletion tools/getnodeversion.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import print_function
import os
import sys


def get_major_minor_patch(text):
Expand All @@ -13,9 +14,30 @@ def get_major_minor_patch(text):
return major, minor, patch


def get_alpha_tag(text):
is_release = False
alpha = {}
for line in text.splitlines():
if line.startswith('#define NODE_VERSION_IS_RELEASE'):
is_release = line.split()[2] == '1'
elif line.startswith('#define NODE_ALPHA_MAJOR_VERSION'):
alpha['major'] = line.split()[2]
elif line.startswith('#define NODE_ALPHA_MINOR_VERSION'):
alpha['minor'] = line.split()[2]
elif line.startswith('#define NODE_ALPHA_PATCH_VERSION'):
alpha['patch'] = line.split()[2]
if not is_release or 'major' not in alpha:
return ''
return '-alpha.' + '.'.join((alpha['major'], alpha['minor'], alpha['patch']))


node_version_h = os.path.join(os.path.dirname(__file__),
'..',
'src',
'node_version.h')
with open(node_version_h) as in_file:
print('.'.join(get_major_minor_patch(in_file.read())))
text = in_file.read()
version = '.'.join(get_major_minor_patch(text))
if '--full' in sys.argv[1:]:
version += get_alpha_tag(text)
print(version)
Loading
Loading