diff --git a/.github/workflows/create-release-proposal.yml b/.github/workflows/create-release-proposal.yml index 735b15ffd12f..84300d8a1695 100644 --- a/.github/workflows/create-release-proposal.yml +++ b/.github/workflows/create-release-proposal.yml @@ -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 }} @@ -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 @@ -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. diff --git a/.github/workflows/lint-release-proposal.yml b/.github/workflows/lint-release-proposal.yml index 9652eea91b89..989a57cd8851 100644 --- a/.github/workflows/lint-release-proposal.yml +++ b/.github/workflows/lint-release-proposal.yml @@ -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 }} @@ -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"'$' diff --git a/.github/workflows/major-release.yml b/.github/workflows/major-release.yml index 1b15a00c8df1..6641571aecc9 100644 --- a/.github/workflows/major-release.yml +++ b/.github/workflows/major-release.yml @@ -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 diff --git a/Makefile b/Makefile index 31d7d911c987..018c41bb8234 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/doc/contributing/releases.md b/doc/contributing/releases.md index 57bf2dc31bf8..e5e93c77ca93 100644 --- a/doc/contributing/releases.md +++ b/doc/contributing/releases.md @@ -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` @@ -903,13 +904,20 @@ git secure-tag -sm "YYYY-MM-DD Node.js vx.y.z (The commit-sha is the release commit. You can get it easily by running `git rev-parse HEAD` -`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. @@ -930,6 +938,9 @@ Working on vx.y.z # where 'z' is the incremented patch number PR-URL: ``` +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. @@ -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 @@ -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. @@ -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. @@ -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, @@ -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 diff --git a/test/parallel/test-release-changelog.js b/test/parallel/test-release-changelog.js index 6b8e6ece43af..1775d7afc7c4 100644 --- a/test/parallel/test-release-changelog.js +++ b/test/parallel/test-release-changelog.js @@ -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); @@ -47,7 +56,7 @@ const changelogPath = `doc/changelogs/CHANGELOG_V${major}.md`; if (lts) { tableHeader = new RegExp(`LTS '${RegExp.escape(codename)}'`); } else { - tableHeader = /Current<\/th>/; + tableHeader = new RegExp(`${releaseType}`); } assert.match(changelog, tableHeader); // Check table contains link to this release. @@ -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); } @@ -72,7 +81,7 @@ 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. @@ -80,7 +89,7 @@ const changelogPath = `doc/changelogs/CHANGELOG_V${major}.md`; if (lts) { tableHeader = new RegExp(`${major} \\(LTS\\)`); } else { - tableHeader = new RegExp(`${major} \\(Current\\)`); + tableHeader = new RegExp(`${major} \\(${releaseType}\\)`); } assert.match(mainChangelog, tableHeader); // Check the table contains a link to the release in the appropriate CHANGELOG_V*.md file. diff --git a/test/parallel/test-release-npm.js b/test/parallel/test-release-npm.js index 5544ef79313a..a9aff0b75794 100644 --- a/test/parallel/test-release-npm.js +++ b/test/parallel/test-release-npm.js @@ -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) { diff --git a/tools/actions/create-release-proposal.sh b/tools/actions/create-release-proposal.sh index 9240fa2ad7f7..673fd070b67a 100755 --- a/tools/actions/create-release-proposal.sh +++ b/tools/actions/create-release-proposal.sh @@ -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 " + echo "Usage: $0 [ []]" exit 1 fi @@ -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^)" diff --git a/tools/actions/lint-release-proposal-commit-list.mjs b/tools/actions/lint-release-proposal-commit-list.mjs index a51f0c590026..fa5ebf79cdfe 100755 --- a/tools/actions/lint-release-proposal-commit-list.mjs +++ b/tools/actions/lint-release-proposal-commit-list.mjs @@ -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>\n\n## \d+-\d+-\d+, Version \1/.exec(changelog); + const releaseStart = /\n<\/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