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
21 changes: 7 additions & 14 deletions apps/site/components/Downloads/Release/VersionDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,14 @@ import { useLocale, useTranslations } from 'next-intl';
import { use } from 'react';

import { redirect, usePathname } from '#site/navigation';
import { STATUS_KIND_MAP } from '#site/next.constants.mjs';
import {
ReleaseContext,
ReleasesContext,
} from '#site/providers/releaseProvider';

import type { FC } from 'react';

const getDropDownStatus = (version: string, status: string) => {
if (status.endsWith('LTS')) {
return `${version} (LTS)`;
}

if (status === 'Current') {
return `${version} (Current)`;
}

return version;
};

const VersionDropdown: FC = () => {
const { releases } = use(ReleasesContext);
const { release, setVersion } = use(ReleaseContext);
Expand All @@ -38,7 +27,7 @@ const VersionDropdown: FC = () => {
({ versionWithPrefix }) => versionWithPrefix === version
);

if (release?.isLts && pathname.includes('current')) {
if (release?.status === 'LTS' && pathname.includes('current')) {
redirect({ href: '/download', locale });
return;
}
Expand All @@ -56,7 +45,11 @@ const VersionDropdown: FC = () => {
ariaLabel={t('layouts.download.dropdown.version')}
values={releases.map(({ status, versionWithPrefix }) => ({
value: versionWithPrefix,
label: getDropDownStatus(versionWithPrefix, status),
label: versionWithPrefix,
badge: {
label: status,
kind: STATUS_KIND_MAP[status],
},
}))}
defaultValue={release.versionWithPrefix}
onChange={setVersionOrNavigate}
Expand Down
5 changes: 1 addition & 4 deletions apps/site/components/EOL/EOLReleaseTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { getTranslations } from 'next-intl/server';

import provideReleaseData from '#site/next-data/providers/releaseData';
import provideVulnerabilities from '#site/next-data/providers/vulnerabilities';
import { EOL_VERSION_IDENTIFIER } from '#site/next.constants.mjs';

import type { FC } from 'react';

Expand All @@ -15,9 +14,7 @@ const EOLReleaseTable: FC = async () => {
const releaseData = await provideReleaseData();
const vulnerabilities = await provideVulnerabilities();

const eolReleases = releaseData.filter(
release => release.status === EOL_VERSION_IDENTIFIER
);
const eolReleases = releaseData.filter(release => release.status === 'EOL');

const t = await getTranslations();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,13 @@ import { Fragment, useState } from 'react';
import FormattedTime from '#site/components/Common/FormattedTime';
import LinkWithArrow from '#site/components/Common/LinkWithArrow';
import Link from '#site/components/Link';
import { STATUS_KIND_MAP } from '#site/next.constants.mjs';

import type { NodeRelease } from '#site/types';
import type { FC } from 'react';

import ReleaseModal from '../ReleaseModal';

const BADGE_KIND_MAP = {
'End-of-life': 'warning',
'Maintenance LTS': 'neutral',
'Active LTS': 'info',
Current: 'default',
Pending: 'default',
} as const;

type PreviousReleasesTableBodyProps = {
releaseData: Array<NodeRelease>;
};
Expand Down Expand Up @@ -50,17 +43,16 @@ const PreviousReleasesTableBody: FC<PreviousReleasesTableBodyProps> = ({
<td
data-label={t('components.downloadReleasesTable.firstReleased')}
>
<FormattedTime date={release.currentStart} />
<FormattedTime date={release.initialDate} />
</td>

<td data-label={t('components.downloadReleasesTable.lastUpdated')}>
<FormattedTime date={release.releaseDate} />
</td>

<td data-label={t('components.downloadReleasesTable.status')}>
<Badge kind={BADGE_KIND_MAP[release.status]} size="small">
<Badge kind={STATUS_KIND_MAP[release.status]} size="small">
{release.status}
{release.status === 'End-of-life' ? ' (EoL)' : ''}
</Badge>
</td>

Expand Down
2 changes: 1 addition & 1 deletion apps/site/components/Releases/ReleaseOverview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const ReleaseOverview: FC<ReleaseOverviewProps> = ({ release }) => {
<div className={styles.container}>
<ReleaseOverviewItem
Icon={CalendarIcon}
title={<FormattedTime date={release.currentStart} />}
title={<FormattedTime date={release.initialDate} />}
subtitle={t('components.releaseOverview.firstReleased')}
/>

Expand Down
4 changes: 1 addition & 3 deletions apps/site/components/withDownloadSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ const WithDownloadSection: FC<WithDownloadSectionProps> = async ({
.concat(localeSnippets);

// Decides which initial release to use based on the current pathname
const initialRelease = pathname.endsWith('/current')
? 'Current'
: ['Active LTS' as const, 'Maintenance LTS' as const];
const initialRelease = pathname.endsWith('/current') ? 'Current' : 'LTS';

return (
<WithNodeRelease status={initialRelease}>
Expand Down
2 changes: 1 addition & 1 deletion apps/site/components/withFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const WithFooter: FC = () => {

const primary = (
<div className="flex flex-row gap-2">
<WithNodeRelease status={['Active LTS', 'Maintenance LTS']}>
<WithNodeRelease status="LTS">
{({ release }) => (
<BadgeGroup
size="small"
Expand Down
5 changes: 2 additions & 3 deletions apps/site/components/withReleaseAlertBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const WithReleaseAlertBox: FC<WithReleaseAlertBoxProps> = ({ status }) => {
const t = useTranslations();

switch (status) {
case 'End-of-life':
case 'EOL':
return (
<AlertBox
title={t('components.common.alertBox.warning')}
Expand All @@ -26,8 +26,7 @@ const WithReleaseAlertBox: FC<WithReleaseAlertBoxProps> = ({ status }) => {
})}
</AlertBox>
);
case 'Active LTS':
case 'Maintenance LTS':
case 'LTS':
return (
<AlertBox
title={t('components.common.alertBox.info')}
Expand Down
183 changes: 155 additions & 28 deletions apps/site/next-data/generators/__tests__/releaseData.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,179 @@ import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

describe('generateReleaseData', () => {
it('generates release data with correct status', async t => {
t.mock.timers.enable({ now: new Date('2024-10-18') });
let currentNodevuData = {};
const nodevuMock = () => Promise.resolve(currentNodevuData);

const runWithNodevuData = async (t, now, data) => {
currentNodevuData = data;
t.mock.timers.enable({ now: new Date(now) });

t.mock.module('@nodevu/core', {
defaultExport: () =>
Promise.resolve({
14: {
releases: {
'14.0.0': {
semver: { major: 14, raw: '14.0.0' },
dependencies: { npm: '6.14.10', v8: '8.0.276.20' },
releaseDate: '2021-04-20',
modules: { version: '83' },
},
},
support: {
phases: {
dates: {
start: '2021-10-26',
lts: '2022-10-18',
maintenance: '2023-10-18',
end: '2024-10-18',
},
},
},
},
}),
defaultExport: nodevuMock,
});

const { default: generateReleaseData } =
await import('#site/next-data/generators/releaseData.mjs');

const result = await generateReleaseData();
return generateReleaseData();
};

it('returns EOL when release is on or past EOL date', async t => {
const result = await runWithNodevuData(t, '2024-10-18', {
14: {
releases: {
'14.0.0': {
semver: { major: 14, raw: '14.0.0' },
dependencies: { npm: '6.14.10', v8: '8.0.276.20' },
releaseDate: '2021-04-20',
modules: { version: '83' },
},
},
support: {
phases: {
dates: {
start: '2021-10-26',
lts: '2022-10-18',
maintenance: '2023-10-18',
end: '2024-10-18',
},
},
},
},
});

assert.equal(result.length, 1);
assert.partialDeepStrictEqual(result[0], {
major: 14,
version: '14.0.0',
versionWithPrefix: 'v14.0.0',
codename: '',
isLts: false,
npm: '6.14.10',
v8: '8.0.276.20',
releaseDate: '2021-04-20',
initialDate: '2021-04-20',
modules: '83',
status: 'End-of-life',
status: 'EOL',
});
});

it('returns Current when release is not EOL and latest is not LTS', async t => {
const result = await runWithNodevuData(t, '2026-04-14', {
20: {
releases: {
'20.12.0': {
semver: { major: 20, raw: '20.12.0' },
dependencies: { npm: '10.8.2', v8: '11.3.244.8' },
lts: { isLts: false },
releaseDate: '2026-03-26',
modules: { version: '115' },
},
},
support: {
phases: {
dates: {
start: '2025-10-22',
lts: '2026-10-22',
maintenance: '2027-10-22',
end: '2028-04-30',
},
},
},
},
});

assert.equal(result[0]?.status, 'Current');
});

it('returns LTS when release is not EOL and latest is flagged as LTS', async t => {
const result = await runWithNodevuData(t, '2026-04-14', {
22: {
releases: {
'22.7.0': {
semver: { major: 22, raw: '22.7.0' },
dependencies: { npm: '10.9.0', v8: '12.4.254.10' },
lts: { isLts: true },
releaseDate: '2026-02-18',
modules: { version: '124' },
},
},
support: {
phases: {
dates: {
start: '2026-04-23',
lts: '2026-10-21',
maintenance: '2027-10-20',
end: '2029-04-30',
},
},
},
},
});

assert.equal(result[0]?.status, 'LTS');
});

it('returns Current when release is not EOL and LTS date has passed but latest is not LTS', async t => {
const result = await runWithNodevuData(t, '2026-04-14', {
24: {
releases: {
'24.1.0': {
semver: { major: 24, raw: '24.1.0' },
dependencies: { npm: '11.1.0', v8: '13.0.12.7' },
lts: { isLts: false },
releaseDate: '2026-03-10',
modules: { version: '130' },
},
},
support: {
phases: {
dates: {
start: '2025-10-10',
lts: '2026-01-01',
maintenance: '2027-01-01',
end: '2028-10-01',
},
},
},
},
});

assert.equal(result[0]?.status, 'Current');
});

it('uses latest and earliest release dates for releaseDate and initialDate', async t => {
const result = await runWithNodevuData(t, '2026-04-14', {
26: {
releases: {
'26.2.0': {
semver: { major: 26, raw: '26.2.0' },
dependencies: { npm: '11.3.1', v8: '13.2.20.1' },
lts: { isLts: false },
releaseDate: '2026-04-01',
modules: { version: '132' },
},
'26.0.0': {
semver: { major: 26, raw: '26.0.0' },
dependencies: { npm: '11.0.0', v8: '13.1.0.0' },
lts: { isLts: false },
releaseDate: '2025-10-21',
modules: { version: '131' },
},
},
support: {
phases: {
dates: {
start: '2025-10-21',
lts: '2026-10-20',
maintenance: '2027-10-19',
end: '2029-04-30',
},
},
},
},
});

assert.equal(result[0]?.releaseDate, '2026-04-01');
assert.equal(result[0]?.initialDate, '2025-10-21');
});
});
Loading
Loading