diff --git a/.changeset/brave-alpha-releases.md b/.changeset/brave-alpha-releases.md new file mode 100644 index 0000000000000..85169618dc240 --- /dev/null +++ b/.changeset/brave-alpha-releases.md @@ -0,0 +1,5 @@ +--- +'@node-core/website-i18n': patch +--- + +Add the Alpha release warning. diff --git a/apps/site/app/[locale]/download/archive/[version]/page.tsx b/apps/site/app/[locale]/download/archive/[version]/page.tsx index cf3a343578e2b..6b16badea89ef 100644 --- a/apps/site/app/[locale]/download/archive/[version]/page.tsx +++ b/apps/site/app/[locale]/download/archive/[version]/page.tsx @@ -51,7 +51,10 @@ const getPage: FC = async props => { if (version === 'current') { const releaseData = await provideReleaseData(); - const release = releaseData.find(release => release.status === 'Current'); + // There's no Current release while the next major is in its Alpha phase + const release = + releaseData.find(release => release.status === 'Current') ?? + releaseData.find(release => release.status === 'LTS'); redirect(`/${locale}/download/archive/${release?.versionWithPrefix}`); } diff --git a/apps/site/components/Downloads/Release/VersionDropdown.tsx b/apps/site/components/Downloads/Release/VersionDropdown.tsx index a2e01b252ab56..e29f41952581f 100644 --- a/apps/site/components/Downloads/Release/VersionDropdown.tsx +++ b/apps/site/components/Downloads/Release/VersionDropdown.tsx @@ -27,7 +27,10 @@ const VersionDropdown: FC = () => { ({ versionWithPrefix }) => versionWithPrefix === version ); - if (release?.status === 'LTS' && pathname.includes('current')) { + if ( + release?.status === 'LTS' && + (pathname.includes('current') || pathname.includes('alpha')) + ) { redirect({ href: '/download', locale }); return; } @@ -37,6 +40,11 @@ const VersionDropdown: FC = () => { return; } + if (release?.status === 'Alpha' && !pathname.includes('alpha')) { + redirect({ href: '/download/alpha', locale }); + return; + } + setVersion(version); }; diff --git a/apps/site/components/withDownloadSection.tsx b/apps/site/components/withDownloadSection.tsx index 373eebaafb290..90d335b1fe4f4 100644 --- a/apps/site/components/withDownloadSection.tsx +++ b/apps/site/components/withDownloadSection.tsx @@ -9,7 +9,7 @@ import { ReleasesProvider, } from '#site/providers/releaseProvider'; -import type { NodeRelease } from '../types'; +import type { NodeRelease, NodeReleaseStatus } from '../types'; import type { FC, PropsWithChildren } from 'react'; type WithDownloadSectionProps = PropsWithChildren<{ @@ -39,7 +39,11 @@ const WithDownloadSection: FC = async ({ .concat(localeSnippets); // Decides which initial release to use based on the current pathname - const initialRelease = pathname.endsWith('/current') ? 'Current' : 'LTS'; + const initialRelease: Array = pathname.endsWith('/alpha') + ? ['Alpha', 'Current', 'LTS'] + : pathname.endsWith('/current') + ? ['Current', 'LTS'] + : ['LTS']; return ( diff --git a/apps/site/components/withReleaseAlertBox.tsx b/apps/site/components/withReleaseAlertBox.tsx index fe57bfe24e44a..c2e896b468612 100644 --- a/apps/site/components/withReleaseAlertBox.tsx +++ b/apps/site/components/withReleaseAlertBox.tsx @@ -26,6 +26,22 @@ const WithReleaseAlertBox: FC = ({ status }) => { })} ); + case 'Alpha': + return ( + + {t.rich('layouts.download.codeBox.alphaVersionWarning', { + link: text => ( + + {text} + + ), + })} + + ); case 'LTS': return ( { semver: { major: 20, raw: '20.12.0' }, dependencies: { npm: '10.8.2', v8: '11.3.244.8' }, lts: { isLts: false }, + alpha: { isAlpha: false }, releaseDate: '2026-03-26', modules: { version: '115' }, }, @@ -82,6 +83,7 @@ describe('generateReleaseData', () => { semver: { major: 22, raw: '22.7.0' }, dependencies: { npm: '10.9.0', v8: '12.4.254.10' }, lts: { isLts: true }, + alpha: { isAlpha: false }, releaseDate: '2026-02-18', modules: { version: '124' }, }, @@ -110,6 +112,7 @@ describe('generateReleaseData', () => { semver: { major: 24, raw: '24.1.0' }, dependencies: { npm: '11.1.0', v8: '13.0.12.7' }, lts: { isLts: false }, + alpha: { isAlpha: false }, releaseDate: '2026-03-10', modules: { version: '130' }, }, @@ -130,6 +133,77 @@ describe('generateReleaseData', () => { assert.equal(result[0]?.status, 'Current'); }); + it('returns Alpha when release is not EOL and latest is a pre-release', async t => { + const result = await runWithNodevuData(t, '2026-11-01', { + 27: { + releases: { + 'v27.0.0-alpha.0.0.0': { + semver: { major: 27, raw: '27.0.0-alpha.0.0.0' }, + dependencies: { npm: '11.6.0', v8: '14.6.0.0' }, + lts: { isLts: false }, + alpha: { isAlpha: true }, + releaseDate: '2026-10-28', + modules: { version: '150' }, + }, + }, + support: { + phases: { + dates: { + alpha: '2026-10-28', + start: '2027-04-22', + maintenance: '2027-10-20', + end: '2030-04-30', + }, + }, + }, + }, + }); + + assert.equal(result[0]?.status, 'Alpha'); + assert.equal(result[0]?.versionWithPrefix, 'v27.0.0-alpha.0.0.0'); + }); + + it('returns Current when latest is a stable release following pre-releases', async t => { + const result = await runWithNodevuData(t, '2027-04-22', { + 27: { + releases: { + 'v27.0.0': { + semver: { major: 27, raw: '27.0.0' }, + dependencies: { npm: '11.6.0', v8: '14.6.0.0' }, + lts: { isLts: false }, + alpha: { isAlpha: false }, + releaseDate: '2027-04-22', + modules: { version: '150' }, + }, + 'v27.0.0-alpha.0.0.0': { + semver: { major: 27, raw: '27.0.0-alpha.0.0.0' }, + dependencies: { npm: '11.6.0', v8: '14.6.0.0' }, + lts: { isLts: false }, + alpha: { isAlpha: true }, + releaseDate: '2026-10-28', + modules: { version: '150' }, + }, + }, + support: { + phases: { + dates: { + alpha: '2026-10-28', + start: '2027-04-22', + maintenance: '2027-10-20', + end: '2030-04-30', + }, + }, + }, + }, + }); + + assert.equal(result[0]?.status, 'Current'); + assert.equal( + result[0]?.minorVersions[1]?.versionWithPrefix, + 'v27.0.0-alpha.0.0.0' + ); + }); + it('uses latest and earliest release dates for latestReleaseDate and initialReleaseDate', async t => { const result = await runWithNodevuData(t, '2026-04-14', { 26: { @@ -138,6 +212,7 @@ describe('generateReleaseData', () => { semver: { major: 26, raw: '26.2.0' }, dependencies: { npm: '11.3.1', v8: '13.2.20.1' }, lts: { isLts: false }, + alpha: { isAlpha: false }, releaseDate: '2026-04-01', modules: { version: '132' }, }, @@ -145,6 +220,7 @@ describe('generateReleaseData', () => { semver: { major: 26, raw: '26.0.0' }, dependencies: { npm: '11.0.0', v8: '13.1.0.0' }, lts: { isLts: false }, + alpha: { isAlpha: false }, releaseDate: '2025-10-21', modules: { version: '131' }, }, diff --git a/apps/site/next-data/generators/releaseData.mjs b/apps/site/next-data/generators/releaseData.mjs index 921d5cff302ba..cdf6e6c203ffc 100644 --- a/apps/site/next-data/generators/releaseData.mjs +++ b/apps/site/next-data/generators/releaseData.mjs @@ -14,6 +14,10 @@ const getNodeReleaseStatus = (latest, eol) => { return 'LTS'; } + if (latest.alpha.isAlpha) { + return 'Alpha'; + } + return 'Current'; }; diff --git a/apps/site/next.constants.mjs b/apps/site/next.constants.mjs index abc3e4da2a87d..b9509bb4c19f3 100644 --- a/apps/site/next.constants.mjs +++ b/apps/site/next.constants.mjs @@ -145,6 +145,7 @@ export const STATUS_KIND_MAP = { EOL: 'warning', LTS: 'info', Current: 'default', + Alpha: 'default', }; /** diff --git a/apps/site/package.json b/apps/site/package.json index 77e46b68c047b..ac8b9deabe374 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -32,7 +32,7 @@ "@node-core/rehype-shiki": "workspace:*", "@node-core/ui-components": "workspace:*", "@node-core/website-i18n": "workspace:*", - "@nodevu/core": "0.3.0", + "@nodevu/core": "1.1.0", "@orama/orama": "^3.1.18", "@radix-ui/react-tabs": "^1.1.18", "@radix-ui/react-tooltip": "^1.2.13", diff --git a/apps/site/pages/en/download/alpha.mdx b/apps/site/pages/en/download/alpha.mdx new file mode 100644 index 0000000000000..cc435c8af1e41 --- /dev/null +++ b/apps/site/pages/en/download/alpha.mdx @@ -0,0 +1,50 @@ +--- +layout: download +title: Download Node.js® +--- + +
+ +Get Node.js® for using with + + + + + +Or get a prebuilt Node.js® for running a architecture. + + + + + +
+ +
+ +Read the changelog or blog post for this version. + +Learn more about [Node.js releases](/about/previous-releases), including the release schedule and LTS status. + +Learn how to verify signed SHASUMS. + +Looking for Node.js source? Download a signed Node.js source tarball. + +Check out our nightly binaries or +all previous releases +or the unofficial binaries for other platforms. + +
+ +--- + +
+

Proudly supported by the partners below:

+ + + We are able to serve Node.js's downloads and maintain our infrastructure + proudly due to the support of these partners, and more. + +
+ +
+
diff --git a/apps/site/scripts/release-post/downloadsTable.mjs b/apps/site/scripts/release-post/downloadsTable.mjs index 1a02c4feb5fc7..1e57631e87e62 100644 --- a/apps/site/scripts/release-post/downloadsTable.mjs +++ b/apps/site/scripts/release-post/downloadsTable.mjs @@ -85,13 +85,16 @@ const resolveUrl = (item, version) => { const resolveDownloads = version => { let downloads = downloadOptions; - if (semVer.satisfies(version, '< 16.0.0')) { + const satisfies = range => + semVer.satisfies(version, range, { includePrerelease: true }); + + if (satisfies('< 16.0.0')) { downloads = downloads.filter( ver => ver.title !== 'macOS Apple Silicon 64-bit Binary' ); } - if (semVer.satisfies(version, '< 19.9.0')) { + if (satisfies('< 19.9.0')) { downloads = downloads.filter( ver => ver.title !== 'Windows ARM 64-bit Installer' && @@ -99,7 +102,7 @@ const resolveDownloads = version => { ); } - if (semVer.satisfies(version, '>= 23.0.0')) { + if (satisfies('>= 23.0.0')) { downloads = downloads.filter( ver => ver.title !== 'Windows 32-bit Installer' && @@ -107,7 +110,7 @@ const resolveDownloads = version => { ); } - if (semVer.satisfies(version, '>= 24.0.0')) { + if (satisfies('>= 24.0.0')) { downloads = downloads.filter(ver => ver.title !== 'ARMv7 32-bit Binary'); } diff --git a/apps/site/types/releases.ts b/apps/site/types/releases.ts index 967fcbeaf512a..8d1c53eafee3d 100644 --- a/apps/site/types/releases.ts +++ b/apps/site/types/releases.ts @@ -1,4 +1,4 @@ -export type NodeReleaseStatus = 'LTS' | 'Current' | 'EOL'; +export type NodeReleaseStatus = 'LTS' | 'Current' | 'Alpha' | 'EOL'; export type NodeReleaseSource = { major: number; diff --git a/apps/site/util/__tests__/download.test.mjs b/apps/site/util/__tests__/download.test.mjs index c1aa3ffd1c655..b6f14982d029c 100644 --- a/apps/site/util/__tests__/download.test.mjs +++ b/apps/site/util/__tests__/download.test.mjs @@ -115,6 +115,28 @@ describe('parseCompat', () => { assert.ok(result[0].disabled); }); + it('should match semver constraints for pre-release versions', () => { + const items = [ + { + value: 'testItem', + compatibility: { semver: ['>= 16.9.0'] }, + }, + { + value: 'testItem2', + compatibility: { semver: ['< 23.0.0'] }, + }, + ]; + const result = parseCompat(items, { + os: 'MAC', + installMethod: '', + platform: 'x64', + version: 'v27.0.0-alpha.0.0.0', + release: { status: 'Alpha' }, + }); + assert.equal(result[0].disabled, false); + assert.ok(result[1].disabled); + }); + it('should disable items if release status is not supported', () => { const items = [ { diff --git a/apps/site/util/download/archive.tsx b/apps/site/util/download/archive.tsx index f5014b3881cbe..f88ed9118d6c2 100644 --- a/apps/site/util/download/archive.tsx +++ b/apps/site/util/download/archive.tsx @@ -64,7 +64,9 @@ const getCompatiblePlatforms = ( } return compatibility.semver.every(version => - semVer.satisfies(versionWithPrefix, version) + semVer.satisfies(versionWithPrefix, version, { + includePrerelease: true, + }) ); }) .map(platform => ({ diff --git a/apps/site/util/download/constants.json b/apps/site/util/download/constants.json index 440637e5b72dc..13a42f929d367 100644 --- a/apps/site/util/download/constants.json +++ b/apps/site/util/download/constants.json @@ -242,5 +242,5 @@ "bitness": ["64", "32"], "architecture": ["arm", "x86"] }, - "statusOrder": ["Current", "LTS", "EOL"] + "statusOrder": ["Alpha", "Current", "LTS", "EOL"] } diff --git a/apps/site/util/download/index.tsx b/apps/site/util/download/index.tsx index 69713f4fb005a..e626890ffaf7b 100644 --- a/apps/site/util/download/index.tsx +++ b/apps/site/util/download/index.tsx @@ -59,7 +59,9 @@ export const parseCompat = < compatibility.installMethod.includes(installMethod), !compatibility.platform || compatibility.platform.includes(platform), !compatibility.semver || - compatibility.semver.every(semver => satisfies(version, semver)), + compatibility.semver.every(semver => + satisfies(version, semver, { includePrerelease: true }) + ), !compatibility.releases || compatibility.releases.includes(release.status), ]; diff --git a/packages/i18n/src/locales/en.json b/packages/i18n/src/locales/en.json index 9d39719cfbe23..f8f05155ff078 100644 --- a/packages/i18n/src/locales/en.json +++ b/packages/i18n/src/locales/en.json @@ -248,6 +248,7 @@ }, "codeBox": { "unsupportedVersionWarning": "This version is out of maintenance. Please use a currently supported version. Understand EOL support.", + "alphaVersionWarning": "Alpha releases are meant for early testing and are not intended for production use. Their APIs may change between releases. Learn more about Alpha releases.", "communityPlatformInfo": "Installation methods that involve community software are supported by the teams maintaining that software.", "externalSupportInfo": "If you encounter any issues please visit {platform}'s website", "noScriptDetected": "This page requires JavaScript. You can download Node.js without JavaScript by visiting the downloads archive page directly.", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 539a0081be605..05afca6a6b778 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -276,8 +276,8 @@ importers: specifier: workspace:* version: link:../../packages/i18n '@nodevu/core': - specifier: 0.3.0 - version: 0.3.0 + specifier: 1.1.0 + version: 1.1.0 '@orama/orama': specifier: ^3.1.18 version: 3.1.18 @@ -2570,8 +2570,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nodevu/core@0.3.0': - resolution: {integrity: sha512-kVJh6kQViCE8Qf1j7pc7yhUke2kKb1eZM+rqln+EytHqyOq+eZUEtfWbDk1vfWhgiknnAcFOhdVA5apT8rTejQ==} + '@nodevu/core@1.1.0': + resolution: {integrity: sha512-wb0p/vJNgIojZt738Qd3JRaCnAwShOKBIkIkmTbpNFJbjWj7iWVl7u4JZfVOBrSpJRiRaVFg49+3xQtBAHtGwA==} '@nodevu/parsefiles@0.0.3': resolution: {integrity: sha512-IjwkVqA2SlH8XweoAw7EJkeyPGk7gW9imlvhpywrKiPCLP1H0meG/blNAF5X38pirsmvhxPe/BCyywT7Exwuow==} @@ -11776,7 +11776,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@nodevu/core@0.3.0': + '@nodevu/core@1.1.0': dependencies: '@nodevu/parsefiles': 0.0.3 luxon: 3.6.1