Skip to content

Commit 358cc10

Browse files
RafaelGSSovflowd
andauthored
feat: support the Alpha release phase (#9223)
* feat: support the Alpha release phase Node.js 27 introduces Alpha releases versioned as vN.0.0-alpha.X.Y.Z (see nodejs/node#63135). - Keep Alpha versions in the release data instead of letting nodevu coerce them into vN.0.0, and give them the new Alpha status. - Add a /download/alpha page and warn that Alpha releases are not meant for production. - Fall back to the latest LTS on /download/current and /download/archive/current while there is no Current release. - Match pre-releases in the platform and installer checks. Refs: nodejs/Release#1187 Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> * chore: bump @nodevu/core to 1.1.0 1.1.0 supports the Alpha release phase: Alpha releases keep their pre-release identifiers instead of being coerced into `vN.0.0`, and every release has an `alpha.isAlpha` flag, like `lts.isLts`. Refs: cutenode/nodevu#86 Assisted-by: Claude Opus 5.5 <noreply@anthropic.com> * refactor: drop the nodevu workaround for Alpha releases nodevu now keeps Alpha releases as they are, so the site no longer strips pre-releases before nodevu parses index.json and rebuilds them afterwards. The Alpha status comes from `alpha.isAlpha`, the same way LTS comes from `lts.isLts`. Assisted-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> Co-authored-by: Claudio Wunder <cwunder@gnome.org>
1 parent d216a65 commit 358cc10

18 files changed

Lines changed: 215 additions & 18 deletions

File tree

‎.changeset/brave-alpha-releases.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@node-core/website-i18n': patch
3+
---
4+
5+
Add the Alpha release warning.

‎apps/site/app/[locale]/download/archive/[version]/page.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ const getPage: FC<PageParams> = async props => {
5151
if (version === 'current') {
5252
const releaseData = await provideReleaseData();
5353

54-
const release = releaseData.find(release => release.status === 'Current');
54+
// There's no Current release while the next major is in its Alpha phase
55+
const release =
56+
releaseData.find(release => release.status === 'Current') ??
57+
releaseData.find(release => release.status === 'LTS');
5558

5659
redirect(`/${locale}/download/archive/${release?.versionWithPrefix}`);
5760
}

‎apps/site/components/Downloads/Release/VersionDropdown.tsx‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ const VersionDropdown: FC = () => {
2727
({ versionWithPrefix }) => versionWithPrefix === version
2828
);
2929

30-
if (release?.status === 'LTS' && pathname.includes('current')) {
30+
if (
31+
release?.status === 'LTS' &&
32+
(pathname.includes('current') || pathname.includes('alpha'))
33+
) {
3134
redirect({ href: '/download', locale });
3235
return;
3336
}
@@ -37,6 +40,11 @@ const VersionDropdown: FC = () => {
3740
return;
3841
}
3942

43+
if (release?.status === 'Alpha' && !pathname.includes('alpha')) {
44+
redirect({ href: '/download/alpha', locale });
45+
return;
46+
}
47+
4048
setVersion(version);
4149
};
4250

‎apps/site/components/withDownloadSection.tsx‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
ReleasesProvider,
1010
} from '#site/providers/releaseProvider';
1111

12-
import type { NodeRelease } from '../types';
12+
import type { NodeRelease, NodeReleaseStatus } from '../types';
1313
import type { FC, PropsWithChildren } from 'react';
1414

1515
type WithDownloadSectionProps = PropsWithChildren<{
@@ -39,7 +39,11 @@ const WithDownloadSection: FC<WithDownloadSectionProps> = async ({
3939
.concat(localeSnippets);
4040

4141
// Decides which initial release to use based on the current pathname
42-
const initialRelease = pathname.endsWith('/current') ? 'Current' : 'LTS';
42+
const initialRelease: Array<NodeReleaseStatus> = pathname.endsWith('/alpha')
43+
? ['Alpha', 'Current', 'LTS']
44+
: pathname.endsWith('/current')
45+
? ['Current', 'LTS']
46+
: ['LTS'];
4347

4448
return (
4549
<WithNodeRelease status={initialRelease}>

‎apps/site/components/withReleaseAlertBox.tsx‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@ const WithReleaseAlertBox: FC<WithReleaseAlertBoxProps> = ({ status }) => {
2626
})}
2727
</AlertBox>
2828
);
29+
case 'Alpha':
30+
return (
31+
<AlertBox
32+
title={t('components.common.alertBox.warning')}
33+
level="warning"
34+
size="small"
35+
>
36+
{t.rich('layouts.download.codeBox.alphaVersionWarning', {
37+
link: text => (
38+
<Link href="/blog/announcements/evolving-the-nodejs-release-schedule">
39+
{text}
40+
</Link>
41+
),
42+
})}
43+
</AlertBox>
44+
);
2945
case 'LTS':
3046
return (
3147
<AlertBox

‎apps/site/next-data/generators/__tests__/releaseData.test.mjs‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ describe('generateReleaseData', () => {
5454
semver: { major: 20, raw: '20.12.0' },
5555
dependencies: { npm: '10.8.2', v8: '11.3.244.8' },
5656
lts: { isLts: false },
57+
alpha: { isAlpha: false },
5758
releaseDate: '2026-03-26',
5859
modules: { version: '115' },
5960
},
@@ -82,6 +83,7 @@ describe('generateReleaseData', () => {
8283
semver: { major: 22, raw: '22.7.0' },
8384
dependencies: { npm: '10.9.0', v8: '12.4.254.10' },
8485
lts: { isLts: true },
86+
alpha: { isAlpha: false },
8587
releaseDate: '2026-02-18',
8688
modules: { version: '124' },
8789
},
@@ -110,6 +112,7 @@ describe('generateReleaseData', () => {
110112
semver: { major: 24, raw: '24.1.0' },
111113
dependencies: { npm: '11.1.0', v8: '13.0.12.7' },
112114
lts: { isLts: false },
115+
alpha: { isAlpha: false },
113116
releaseDate: '2026-03-10',
114117
modules: { version: '130' },
115118
},
@@ -130,6 +133,77 @@ describe('generateReleaseData', () => {
130133
assert.equal(result[0]?.status, 'Current');
131134
});
132135

136+
it('returns Alpha when release is not EOL and latest is a pre-release', async t => {
137+
const result = await runWithNodevuData(t, '2026-11-01', {
138+
27: {
139+
releases: {
140+
'v27.0.0-alpha.0.0.0': {
141+
semver: { major: 27, raw: '27.0.0-alpha.0.0.0' },
142+
dependencies: { npm: '11.6.0', v8: '14.6.0.0' },
143+
lts: { isLts: false },
144+
alpha: { isAlpha: true },
145+
releaseDate: '2026-10-28',
146+
modules: { version: '150' },
147+
},
148+
},
149+
support: {
150+
phases: {
151+
dates: {
152+
alpha: '2026-10-28',
153+
start: '2027-04-22',
154+
maintenance: '2027-10-20',
155+
end: '2030-04-30',
156+
},
157+
},
158+
},
159+
},
160+
});
161+
162+
assert.equal(result[0]?.status, 'Alpha');
163+
assert.equal(result[0]?.versionWithPrefix, 'v27.0.0-alpha.0.0.0');
164+
});
165+
166+
it('returns Current when latest is a stable release following pre-releases', async t => {
167+
const result = await runWithNodevuData(t, '2027-04-22', {
168+
27: {
169+
releases: {
170+
'v27.0.0': {
171+
semver: { major: 27, raw: '27.0.0' },
172+
dependencies: { npm: '11.6.0', v8: '14.6.0.0' },
173+
lts: { isLts: false },
174+
alpha: { isAlpha: false },
175+
releaseDate: '2027-04-22',
176+
modules: { version: '150' },
177+
},
178+
'v27.0.0-alpha.0.0.0': {
179+
semver: { major: 27, raw: '27.0.0-alpha.0.0.0' },
180+
dependencies: { npm: '11.6.0', v8: '14.6.0.0' },
181+
lts: { isLts: false },
182+
alpha: { isAlpha: true },
183+
releaseDate: '2026-10-28',
184+
modules: { version: '150' },
185+
},
186+
},
187+
support: {
188+
phases: {
189+
dates: {
190+
alpha: '2026-10-28',
191+
start: '2027-04-22',
192+
maintenance: '2027-10-20',
193+
end: '2030-04-30',
194+
},
195+
},
196+
},
197+
},
198+
});
199+
200+
assert.equal(result[0]?.status, 'Current');
201+
assert.equal(
202+
result[0]?.minorVersions[1]?.versionWithPrefix,
203+
'v27.0.0-alpha.0.0.0'
204+
);
205+
});
206+
133207
it('uses latest and earliest release dates for latestReleaseDate and initialReleaseDate', async t => {
134208
const result = await runWithNodevuData(t, '2026-04-14', {
135209
26: {
@@ -138,13 +212,15 @@ describe('generateReleaseData', () => {
138212
semver: { major: 26, raw: '26.2.0' },
139213
dependencies: { npm: '11.3.1', v8: '13.2.20.1' },
140214
lts: { isLts: false },
215+
alpha: { isAlpha: false },
141216
releaseDate: '2026-04-01',
142217
modules: { version: '132' },
143218
},
144219
'26.0.0': {
145220
semver: { major: 26, raw: '26.0.0' },
146221
dependencies: { npm: '11.0.0', v8: '13.1.0.0' },
147222
lts: { isLts: false },
223+
alpha: { isAlpha: false },
148224
releaseDate: '2025-10-21',
149225
modules: { version: '131' },
150226
},

‎apps/site/next-data/generators/releaseData.mjs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ const getNodeReleaseStatus = (latest, eol) => {
1414
return 'LTS';
1515
}
1616

17+
if (latest.alpha.isAlpha) {
18+
return 'Alpha';
19+
}
20+
1721
return 'Current';
1822
};
1923

‎apps/site/next.constants.mjs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export const STATUS_KIND_MAP = {
145145
EOL: 'warning',
146146
LTS: 'info',
147147
Current: 'default',
148+
Alpha: 'default',
148149
};
149150

150151
/**

‎apps/site/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"@node-core/rehype-shiki": "workspace:*",
3333
"@node-core/ui-components": "workspace:*",
3434
"@node-core/website-i18n": "workspace:*",
35-
"@nodevu/core": "0.3.0",
35+
"@nodevu/core": "1.1.0",
3636
"@orama/orama": "^3.1.18",
3737
"@radix-ui/react-tabs": "^1.1.18",
3838
"@radix-ui/react-tooltip": "^1.2.13",
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
layout: download
3+
title: Download Node.js®
4+
---
5+
6+
<section>
7+
8+
Get Node.js® <Release.VersionDropdown /> for <Release.OperatingSystemDropdown /> using <Release.InstallationMethodDropdown /> with <Release.PackageManagerDropdown />
9+
10+
<Release.ReleaseCodeBox />
11+
12+
<Release.Provider>
13+
14+
Or get a prebuilt Node.js® for <Release.OperatingSystemDropdown /> running a <Release.PlatformDropdown /> architecture.
15+
16+
<Release.PrebuiltDownloadButtons />
17+
18+
</Release.Provider>
19+
20+
</section>
21+
22+
<section>
23+
24+
Read the <Release.ChangelogLink>changelog</Release.ChangelogLink> or <Release.BlogPostLink>blog post</Release.BlogPostLink> for this version.
25+
26+
Learn more about [Node.js releases](/about/previous-releases), including the release schedule and LTS status.
27+
28+
Learn how to <LinkWithArrow href="https://github.com/nodejs/node#verifying-binaries">verify</LinkWithArrow> signed SHASUMS.
29+
30+
Looking for Node.js source? Download a signed <Release.DownloadLink kind="source">Node.js source</Release.DownloadLink> tarball.
31+
32+
Check out our <LinkWithArrow href="https://nodejs.org/download/nightly/">nightly</LinkWithArrow> binaries or
33+
all <Link href="/download/archive/current">previous releases</Link>
34+
or the <LinkWithArrow href="https://unofficial-builds.nodejs.org/download/">unofficial</LinkWithArrow> binaries for other platforms.
35+
36+
</section>
37+
38+
---
39+
40+
<section className="mt-3">
41+
<h2 className='text-center'>Proudly supported by the partners below:</h2>
42+
43+
<span className="text-center">
44+
We are able to serve Node.js's downloads and maintain our infrastructure
45+
proudly due to the support of these partners, <Link href="/about/partners">and more</Link>.
46+
</span>
47+
<div className='mt-4'>
48+
<PartnersList size="large" category="infrastructure" length={3} sort="weight" />
49+
</div>
50+
</section>

0 commit comments

Comments
 (0)