Skip to content

Commit 5a6d7d2

Browse files
authored
chore: remove Active/Maintenance LTS distinction (#8817)
* Remove Active/Maintenance LTS distinction * Use Badges for release status in version dropdown * Improve test coverage for generateReleaseData logic * Consistently use EOL identifier * Clarify release date values naming
1 parent ba1a0ca commit 5a6d7d2

File tree

17 files changed

+251
-138
lines changed

17 files changed

+251
-138
lines changed

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

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,14 @@ import { useLocale, useTranslations } from 'next-intl';
55
import { use } from 'react';
66

77
import { redirect, usePathname } from '#site/navigation';
8+
import { STATUS_KIND_MAP } from '#site/next.constants.mjs';
89
import {
910
ReleaseContext,
1011
ReleasesContext,
1112
} from '#site/providers/releaseProvider';
1213

1314
import type { FC } from 'react';
1415

15-
const getDropDownStatus = (version: string, status: string) => {
16-
if (status.endsWith('LTS')) {
17-
return `${version} (LTS)`;
18-
}
19-
20-
if (status === 'Current') {
21-
return `${version} (Current)`;
22-
}
23-
24-
return version;
25-
};
26-
2716
const VersionDropdown: FC = () => {
2817
const { releases } = use(ReleasesContext);
2918
const { release, setVersion } = use(ReleaseContext);
@@ -38,7 +27,7 @@ const VersionDropdown: FC = () => {
3827
({ versionWithPrefix }) => versionWithPrefix === version
3928
);
4029

41-
if (release?.isLts && pathname.includes('current')) {
30+
if (release?.status === 'LTS' && pathname.includes('current')) {
4231
redirect({ href: '/download', locale });
4332
return;
4433
}
@@ -56,7 +45,8 @@ const VersionDropdown: FC = () => {
5645
ariaLabel={t('layouts.download.dropdown.version')}
5746
values={releases.map(({ status, versionWithPrefix }) => ({
5847
value: versionWithPrefix,
59-
label: getDropDownStatus(versionWithPrefix, status),
48+
label: versionWithPrefix,
49+
badge: { label: status, kind: STATUS_KIND_MAP[status] },
6050
}))}
6151
defaultValue={release.versionWithPrefix}
6252
onChange={setVersionOrNavigate}

apps/site/components/EOL/EOLReleaseTable/TableBody.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const EOLReleaseTableBody: FC<EOLReleaseTableBodyProps> = ({
3434
</td>
3535

3636
<td data-label="Date">
37-
<FormattedTime date={release.releaseDate} />
37+
<FormattedTime date={release.latestReleaseDate} />
3838
</td>
3939

4040
<td>

apps/site/components/EOL/EOLReleaseTable/index.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { getTranslations } from 'next-intl/server';
33

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

87
import type { FC } from 'react';
98

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

18-
const eolReleases = releaseData.filter(
19-
release => release.status === EOL_VERSION_IDENTIFIER
20-
);
17+
const eolReleases = releaseData.filter(release => release.status === 'EOL');
2118

2219
const t = await getTranslations();
2320

apps/site/components/Releases/PreviousReleasesTable/TableBody.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,13 @@ import { Fragment, useState } from 'react';
77
import FormattedTime from '#site/components/Common/FormattedTime';
88
import LinkWithArrow from '#site/components/Common/LinkWithArrow';
99
import Link from '#site/components/Link';
10+
import { STATUS_KIND_MAP } from '#site/next.constants.mjs';
1011

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

1415
import ReleaseModal from '../ReleaseModal';
1516

16-
const BADGE_KIND_MAP = {
17-
'End-of-life': 'warning',
18-
'Maintenance LTS': 'neutral',
19-
'Active LTS': 'info',
20-
Current: 'default',
21-
Pending: 'default',
22-
} as const;
23-
2417
type PreviousReleasesTableBodyProps = {
2518
releaseData: Array<NodeRelease>;
2619
};
@@ -50,17 +43,16 @@ const PreviousReleasesTableBody: FC<PreviousReleasesTableBodyProps> = ({
5043
<td
5144
data-label={t('components.downloadReleasesTable.firstReleased')}
5245
>
53-
<FormattedTime date={release.currentStart} />
46+
<FormattedTime date={release.initialReleaseDate} />
5447
</td>
5548

5649
<td data-label={t('components.downloadReleasesTable.lastUpdated')}>
57-
<FormattedTime date={release.releaseDate} />
50+
<FormattedTime date={release.latestReleaseDate} />
5851
</td>
5952

6053
<td data-label={t('components.downloadReleasesTable.status')}>
61-
<Badge kind={BADGE_KIND_MAP[release.status]} size="small">
54+
<Badge kind={STATUS_KIND_MAP[release.status]} size="small">
6255
{release.status}
63-
{release.status === 'End-of-life' ? ' (EoL)' : ''}
6456
</Badge>
6557
</td>
6658

apps/site/components/Releases/ReleaseOverview/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ const ReleaseOverview: FC<ReleaseOverviewProps> = ({ release }) => {
2828
<div className={styles.container}>
2929
<ReleaseOverviewItem
3030
Icon={CalendarIcon}
31-
title={<FormattedTime date={release.currentStart} />}
31+
title={<FormattedTime date={release.initialReleaseDate} />}
3232
subtitle={t('components.releaseOverview.firstReleased')}
3333
/>
3434

3535
<ReleaseOverviewItem
3636
Icon={ClockIcon}
37-
title={<FormattedTime date={release.releaseDate} />}
37+
title={<FormattedTime date={release.latestReleaseDate} />}
3838
subtitle={t('components.releaseOverview.lastUpdated')}
3939
/>
4040

apps/site/components/withDownloadSection.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ 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')
43-
? 'Current'
44-
: ['Active LTS' as const, 'Maintenance LTS' as const];
42+
const initialRelease = pathname.endsWith('/current') ? 'Current' : 'LTS';
4543

4644
return (
4745
<WithNodeRelease status={initialRelease}>

apps/site/components/withFooter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const WithFooter: FC = () => {
2727

2828
const primary = (
2929
<div className="flex flex-row gap-2">
30-
<WithNodeRelease status={['Active LTS', 'Maintenance LTS']}>
30+
<WithNodeRelease status="LTS">
3131
{({ release }) => (
3232
<BadgeGroup
3333
size="small"

apps/site/components/withReleaseAlertBox.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const WithReleaseAlertBox: FC<WithReleaseAlertBoxProps> = ({ status }) => {
1414
const t = useTranslations();
1515

1616
switch (status) {
17-
case 'End-of-life':
17+
case 'EOL':
1818
return (
1919
<AlertBox
2020
title={t('components.common.alertBox.warning')}
@@ -26,8 +26,7 @@ const WithReleaseAlertBox: FC<WithReleaseAlertBoxProps> = ({ status }) => {
2626
})}
2727
</AlertBox>
2828
);
29-
case 'Active LTS':
30-
case 'Maintenance LTS':
29+
case 'LTS':
3130
return (
3231
<AlertBox
3332
title={t('components.common.alertBox.info')}

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

Lines changed: 153 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,167 @@ import assert from 'node:assert/strict';
22
import { describe, it } from 'node:test';
33

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

812
t.mock.module('@nodevu/core', {
9-
defaultExport: () =>
10-
Promise.resolve({
11-
14: {
12-
releases: {
13-
'14.0.0': {
14-
semver: { major: 14, raw: '14.0.0' },
15-
dependencies: { npm: '6.14.10', v8: '8.0.276.20' },
16-
releaseDate: '2021-04-20',
17-
modules: { version: '83' },
18-
},
13+
defaultExport: nodevuMock,
14+
});
15+
16+
const { default: generateReleaseData } =
17+
await import('#site/next-data/generators/releaseData.mjs');
18+
19+
return generateReleaseData();
20+
};
21+
22+
it('returns EOL when release is on or past EOL date', async t => {
23+
const result = await runWithNodevuData(t, '2024-10-18', {
24+
14: {
25+
releases: {
26+
'14.0.0': {
27+
semver: { major: 14, raw: '14.0.0' },
28+
dependencies: { npm: '6.14.10', v8: '8.0.276.20' },
29+
releaseDate: '2021-04-20',
30+
modules: { version: '83' },
31+
},
32+
},
33+
support: {
34+
phases: {
35+
dates: {
36+
start: '2021-10-26',
37+
lts: '2022-10-18',
38+
maintenance: '2023-10-18',
39+
end: '2024-10-18',
1940
},
20-
support: {
21-
phases: {
22-
dates: {
23-
start: '2021-10-26',
24-
lts: '2022-10-18',
25-
maintenance: '2023-10-18',
26-
end: '2024-10-18',
27-
},
28-
},
41+
},
42+
},
43+
},
44+
});
45+
46+
assert.equal(result[0]?.status, 'EOL');
47+
});
48+
49+
it('returns Current when release is not EOL and latest is not LTS', async t => {
50+
const result = await runWithNodevuData(t, '2026-04-14', {
51+
20: {
52+
releases: {
53+
'20.12.0': {
54+
semver: { major: 20, raw: '20.12.0' },
55+
dependencies: { npm: '10.8.2', v8: '11.3.244.8' },
56+
lts: { isLts: false },
57+
releaseDate: '2026-03-26',
58+
modules: { version: '115' },
59+
},
60+
},
61+
support: {
62+
phases: {
63+
dates: {
64+
start: '2025-10-22',
65+
lts: '2026-10-22',
66+
maintenance: '2027-10-22',
67+
end: '2028-04-30',
2968
},
3069
},
31-
}),
70+
},
71+
},
3272
});
3373

34-
const { default: generateReleaseData } =
35-
await import('#site/next-data/generators/releaseData.mjs');
74+
assert.equal(result[0]?.status, 'Current');
75+
});
76+
77+
it('returns LTS when release is not EOL and latest is flagged as LTS', async t => {
78+
const result = await runWithNodevuData(t, '2026-04-14', {
79+
22: {
80+
releases: {
81+
'22.7.0': {
82+
semver: { major: 22, raw: '22.7.0' },
83+
dependencies: { npm: '10.9.0', v8: '12.4.254.10' },
84+
lts: { isLts: true },
85+
releaseDate: '2026-02-18',
86+
modules: { version: '124' },
87+
},
88+
},
89+
support: {
90+
phases: {
91+
dates: {
92+
start: '2026-04-23',
93+
lts: '2026-10-21',
94+
maintenance: '2027-10-20',
95+
end: '2029-04-30',
96+
},
97+
},
98+
},
99+
},
100+
});
101+
102+
assert.equal(result[0]?.status, 'LTS');
103+
});
104+
105+
it('returns Current when release is not EOL and LTS date has passed but latest is not LTS', async t => {
106+
const result = await runWithNodevuData(t, '2026-04-14', {
107+
24: {
108+
releases: {
109+
'24.1.0': {
110+
semver: { major: 24, raw: '24.1.0' },
111+
dependencies: { npm: '11.1.0', v8: '13.0.12.7' },
112+
lts: { isLts: false },
113+
releaseDate: '2026-03-10',
114+
modules: { version: '130' },
115+
},
116+
},
117+
support: {
118+
phases: {
119+
dates: {
120+
start: '2025-10-10',
121+
lts: '2026-01-01',
122+
maintenance: '2027-01-01',
123+
end: '2028-10-01',
124+
},
125+
},
126+
},
127+
},
128+
});
36129

37-
const result = await generateReleaseData();
38-
39-
assert.equal(result.length, 1);
40-
assert.partialDeepStrictEqual(result[0], {
41-
major: 14,
42-
version: '14.0.0',
43-
versionWithPrefix: 'v14.0.0',
44-
codename: '',
45-
isLts: false,
46-
npm: '6.14.10',
47-
v8: '8.0.276.20',
48-
releaseDate: '2021-04-20',
49-
modules: '83',
50-
status: 'End-of-life',
130+
assert.equal(result[0]?.status, 'Current');
131+
});
132+
133+
it('uses latest and earliest release dates for latestReleaseDate and initialReleaseDate', async t => {
134+
const result = await runWithNodevuData(t, '2026-04-14', {
135+
26: {
136+
releases: {
137+
'26.2.0': {
138+
semver: { major: 26, raw: '26.2.0' },
139+
dependencies: { npm: '11.3.1', v8: '13.2.20.1' },
140+
lts: { isLts: false },
141+
releaseDate: '2026-04-01',
142+
modules: { version: '132' },
143+
},
144+
'26.0.0': {
145+
semver: { major: 26, raw: '26.0.0' },
146+
dependencies: { npm: '11.0.0', v8: '13.1.0.0' },
147+
lts: { isLts: false },
148+
releaseDate: '2025-10-21',
149+
modules: { version: '131' },
150+
},
151+
},
152+
support: {
153+
phases: {
154+
dates: {
155+
start: '2025-10-21',
156+
lts: '2026-10-20',
157+
maintenance: '2027-10-19',
158+
end: '2029-04-30',
159+
},
160+
},
161+
},
162+
},
51163
});
164+
165+
assert.equal(result[0]?.latestReleaseDate, '2026-04-01');
166+
assert.equal(result[0]?.initialReleaseDate, '2025-10-21');
52167
});
53168
});

0 commit comments

Comments
 (0)