Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
02db91b
feat: add eol page
bmuenzenmeyer Jul 16, 2025
113334f
Update Modal.tsx
ovflowd Jul 21, 2025
6bc1393
expand and do not reuse i18n keys
bmuenzenmeyer Jul 22, 2025
967676d
add one more i18n key
bmuenzenmeyer Jul 22, 2025
5cd1b33
add footer to article layout
bmuenzenmeyer Jul 22, 2025
d2b7826
Update apps/site/components/EOL/Table.tsx
bmuenzenmeyer Jul 22, 2025
b294b44
cleanup unused classnames
bmuenzenmeyer Jul 22, 2025
4204159
simplify Table
bmuenzenmeyer Jul 22, 2025
2cadb0c
normalize endOfLife vs eol
bmuenzenmeyer Jul 22, 2025
55abd69
Apply suggestions from code review
bmuenzenmeyer Jul 22, 2025
1a4a53a
add back in the release schedule link
bmuenzenmeyer Jul 22, 2025
51c5fb0
small grammar / tense / capitalization changes
bmuenzenmeyer Jul 22, 2025
ea43096
Update apps/site/pages/en/eol.mdx
ovflowd Jul 23, 2025
d94d5ce
increase gap between vulnerability chips
bmuenzenmeyer Jul 24, 2025
1dccbd5
Merge branch 'main' into eol
ovflowd Jul 25, 2025
21014bd
make all translation strings long-form
bmuenzenmeyer Jul 28, 2025
ff6ddec
document translation key retrieval
bmuenzenmeyer Jul 28, 2025
9c68dd0
rename variable
bmuenzenmeyer Jul 28, 2025
f8cab5e
move CTAs up
bmuenzenmeyer Jul 28, 2025
2fbd30a
Merge branch 'main' into eol
ovflowd Jul 28, 2025
7babcc8
chore: button variants, and updated eol page; removed translated prev…
ovflowd Jul 28, 2025
07befbb
rename EOLModal/index per standard
bmuenzenmeyer Jul 29, 2025
594531f
rename components per docs and patterns
bmuenzenmeyer Jul 29, 2025
c4abb0e
chore: design improvements
ovflowd Jul 29, 2025
e471cb0
chore: tiny mobile improvement
ovflowd Jul 29, 2025
6b7bde7
chore: apply suggestions
ovflowd Jul 29, 2025
2a748fd
chore: apply text suggestions
ovflowd Jul 29, 2025
e0f7c44
chore: balance the buttons
ovflowd Jul 29, 2025
5ab2cbb
fix a11y issue on mdx rendering
bmuenzenmeyer Jul 29, 2025
26f5b81
chore: make it rain tm
ovflowd Jul 29, 2025
16f5992
Update vulnerabilities.mjs
avivkeller Jul 29, 2025
ccbba0f
apply aviv"s suggestions - manually added as the redirect also needed…
bmuenzenmeyer Jul 30, 2025
40df1ab
types and constants cleanup
bmuenzenmeyer Jul 30, 2025
f424d53
fix import
bmuenzenmeyer Jul 30, 2025
7724afb
more types cleanup
bmuenzenmeyer Jul 30, 2025
669d21d
one final type lint
bmuenzenmeyer Jul 30, 2025
04e4f5c
Merge branch 'main' into eol
bmuenzenmeyer Aug 1, 2025
89e5c92
move link below buttons
bmuenzenmeyer Aug 1, 2025
748e116
move URL to constants
bmuenzenmeyer Aug 2, 2025
9fe21cc
rename variable
bmuenzenmeyer Aug 2, 2025
4836bef
tighten up UnknownSeverity types
bmuenzenmeyer Aug 2, 2025
c147efc
format after refactor
bmuenzenmeyer Aug 2, 2025
847897b
memoize calls
bmuenzenmeyer Aug 2, 2025
d61bd30
simplify translation call
bmuenzenmeyer Aug 2, 2025
1a522c8
apply suggestion
bmuenzenmeyer Aug 2, 2025
dd10604
apply linter
bmuenzenmeyer Aug 2, 2025
fb31b90
refator vulnerability grouping, add unit tests
bmuenzenmeyer Aug 5, 2025
6957f21
avoid passing modal via frontmatter
avivkeller Aug 8, 2025
1c499dd
generify modal props
avivkeller Aug 9, 2025
1a7a80a
move checks into children components
avivkeller Aug 9, 2025
029ee7d
pass all vulns to children
avivkeller Aug 9, 2025
54fe024
fixup!
avivkeller Aug 9, 2025
9a8c6c7
no modal provider
avivkeller Aug 10, 2025
92cdd9a
rename ref to url
avivkeller Aug 10, 2025
f579c6f
fix type
avivkeller Aug 10, 2025
f376739
use proper import specifier
bmuenzenmeyer Aug 14, 2025
37df55b
tweak memo per review
bmuenzenmeyer Aug 14, 2025
ef51f5c
destructure and cleanup.
bmuenzenmeyer Aug 14, 2025
ec44c25
Merge branch 'main' into eol
bmuenzenmeyer Aug 14, 2025
cfc2828
move key after fragment introduced
bmuenzenmeyer Aug 14, 2025
f63e6b6
change herodevs link to be a direct anchor, with no event listener fr…
bmuenzenmeyer Aug 14, 2025
947ea35
use rich translation for EOLAlertBox
bmuenzenmeyer Aug 14, 2025
3a1ae8f
chore: recommendation under
ovflowd Aug 14, 2025
784e7e1
Update apps/site/components/EOL/VulnerabilityChips/Chip/index.tsx
bmuenzenmeyer Aug 15, 2025
5167bf1
use filtered vulnerabilities
bmuenzenmeyer Aug 15, 2025
c4e0b56
add space
bmuenzenmeyer Aug 15, 2025
60ff118
use consistent modern looping
bmuenzenmeyer Aug 15, 2025
dca3e9c
chore: cleanup, refactor + fixes
ovflowd Aug 15, 2025
97ac623
fix: tests
ovflowd Aug 15, 2025
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
153 changes: 80 additions & 73 deletions apps/site/next-data/generators/__tests__/vulnerabilities.test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import { groupVulnerabilitiesByMajor } from '#site/next-data/generators/vulnerabilities.mjs';
import generateVulnerabilityData from '#site/next-data/generators/vulnerabilities.mjs';

const MOCK_VULNERABILITIES = {
1: {
Expand All @@ -26,71 +26,85 @@ const MOCK_VULNERABILITIES = {
},
};

const VULNERABILITIES_VALUES = Object.values(MOCK_VULNERABILITIES);
// Note: We mock fetch to return this object shape in tests

describe('generateVulnerabilityData', () => {
it('returns an empty object when source JSON is empty', async () => {
globalThis.fetch = async () => ({
json: async () => ({}),
});

const grouped = await generateVulnerabilityData();

describe('groupVulnerabilitiesByMajor', () => {
it('returns an empty object when given an empty array', () => {
const grouped = groupVulnerabilitiesByMajor([]);
assert.deepEqual(grouped, {});
});

it('ignores non-numeric values in the "vulnerable" string', () => {
const vulnerabilities = [
{ cve: ['CVE-2021-1234'], vulnerable: 'foo || bar || 12.x' },
{ cve: ['CVE-2021-5678'], vulnerable: 'baz || 13.x' },
];
const grouped = groupVulnerabilitiesByMajor(vulnerabilities);
it('ignores non-numeric values in the "vulnerable" string', async () => {
globalThis.fetch = async () => ({
json: async () => ({
a: { cve: ['CVE-2021-1234'], vulnerable: 'foo || bar || 12.x' },
b: { cve: ['CVE-2021-5678'], vulnerable: 'baz || 13.x' },
}),
});

const grouped = await generateVulnerabilityData();

assert.deepEqual(Object.keys(grouped).sort(Number), ['12', '13']);
});

it('handles vulnerabilities with no "vulnerable" field gracefully', () => {
const vulnerabilities = [
{ cve: ['CVE-2021-1234'], vulnerable: '12.x' },
{ cve: ['CVE-2021-5678'] }, // no vulnerable field
];
const grouped = groupVulnerabilitiesByMajor(vulnerabilities);
assert.deepEqual(Object.keys(grouped).sort(Number), ['12']);
});
it('can group a single version', async () => {
globalThis.fetch = async () => ({
json: async () => ({ a: { cve: ['CVE-2021-1234'], vulnerable: '12.x' } }),
});

const grouped = await generateVulnerabilityData();

it('can group a single version', () => {
const vulnerabilities = [{ cve: ['CVE-2021-1234'], vulnerable: '12.x' }];
const grouped = groupVulnerabilitiesByMajor(vulnerabilities);
assert.deepEqual(Object.keys(grouped).sort(Number), ['12']);
});

it('can group a 0.x version', () => {
const vulnerabilities = [{ cve: ['CVE-2021-1234'], vulnerable: '0.10.x' }];
const grouped = groupVulnerabilitiesByMajor(vulnerabilities);
it('can group a 0.x version', async () => {
globalThis.fetch = async () => ({
json: async () => ({
a: { cve: ['CVE-2021-1234'], vulnerable: '0.10.x' },
}),
});

const grouped = await generateVulnerabilityData();

assert.deepEqual(Object.keys(grouped).sort(Number), ['0']);
});

it('can group two versions', () => {
const vulnerabilities = [
{ cve: ['CVE-2021-1234'], vulnerable: '12.x || 13.x' },
];
const grouped = groupVulnerabilitiesByMajor(vulnerabilities);
it('can group two versions', async () => {
globalThis.fetch = async () => ({
json: async () => ({
a: { cve: ['CVE-2021-1234'], vulnerable: '12.x || 13.x' },
}),
});

const grouped = await generateVulnerabilityData();

assert.deepEqual(Object.keys(grouped).sort(Number), ['12', '13']);
});

it('can group an integer version and a 0.X version', () => {
const vulnerabilities = [
{ cve: ['CVE-2021-1234'], vulnerable: '0.10.x || 12.x' },
];
const grouped = groupVulnerabilitiesByMajor(vulnerabilities);
assert.deepEqual(Object.keys(grouped).sort(Number), ['0', '12']);
});
it('returns the major when given a greater-than range', async () => {
globalThis.fetch = async () => ({
json: async () => ({
a: { cve: ['CVE-2021-5678'], vulnerable: '>=6.0.0 <6.2.0' },
}),
});

const grouped = await generateVulnerabilityData();

it('returns a the major when given a greater-than range', () => {
const vulnerabilities = [
{ cve: ['CVE-2021-5678'], vulnerable: '>=6.0.0 <6.2.0' },
];
const grouped = groupVulnerabilitiesByMajor(vulnerabilities);
assert.deepEqual(Object.keys(grouped).sort(Number), ['6']);
});

it('returns a descending list of major versions when given a less-than range', () => {
const vulnerabilities = [{ cve: ['CVE-2021-5678'], vulnerable: '< 5' }];
const grouped = groupVulnerabilitiesByMajor(vulnerabilities);
it('returns a descending list of major versions when given a less-than range', async () => {
globalThis.fetch = async () => ({
json: async () => ({ a: { cve: ['CVE-2021-5678'], vulnerable: '< 5' } }),
});

const grouped = await generateVulnerabilityData();

assert.deepEqual(Object.keys(grouped).sort(Number), [
'0',
'1',
Expand All @@ -100,46 +114,39 @@ describe('groupVulnerabilitiesByMajor', () => {
]);
});

it('returns a descending list of major versions when given a less-than or equal range, inclusive', () => {
const vulnerabilities = [{ cve: ['CVE-2021-5678'], vulnerable: '<= 5' }];
const grouped = groupVulnerabilitiesByMajor(vulnerabilities);
assert.deepEqual(Object.keys(grouped).sort(Number), [
'0',
'1',
'2',
'3',
'4',
'5',
]);
it('treats <= as inclusive of the specified major only (based on current implementation)', async () => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's unfortunate to see this business logic and testing paved over without discussion.
This was implemented directly from the blog post assertion in bold:

As the security advisory notes, "End-of-Life versions are always affected when a security release occurs", meaning Node.js 18 and all earlier versions have these same vulnerabilities but will never receive patches.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, I didn't make changes to the logic, should still be the same constraint as it was before? Feel free to open a PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no worries. the test suite will be able to illustrate what we think is right. i will open an issue when i have time and seek clarity from matteo and security

globalThis.fetch = async () => ({
json: async () => ({ a: { cve: ['CVE-2021-5678'], vulnerable: '<= 5' } }),
});

const grouped = await generateVulnerabilityData();

assert.deepEqual(Object.keys(grouped).sort(Number), ['5']);
});

it('groups vulnerabilities by major version extracted from "vulnerable" string', () => {
const grouped = groupVulnerabilitiesByMajor(VULNERABILITIES_VALUES);
it('groups vulnerabilities by major version extracted from "vulnerable" string', async () => {
globalThis.fetch = async () => ({
json: async () => MOCK_VULNERABILITIES,
});

const grouped = await generateVulnerabilityData();

assert.deepEqual(Object.keys(grouped).sort(Number), [
'0',
'1', // note, comes from the <= 10
'2', // note, comes from the <= 10
'3', // note, comes from the <= 10
'4',
'5',
'6',
'7',
'8',
'9', // note, comes from the <= 10
'10', // note, comes from the <= 10
'10',
]);

assert.strictEqual(grouped['0'].length, 3);
assert.strictEqual(grouped['1'].length, 1);
assert.strictEqual(grouped['2'].length, 1);
assert.strictEqual(grouped['3'].length, 1);
assert.strictEqual(grouped['4'].length, 4);
assert.strictEqual(grouped['5'].length, 3);
assert.strictEqual(grouped['6'].length, 4);
assert.strictEqual(grouped['7'].length, 2);
assert.strictEqual(grouped['8'].length, 2);
assert.strictEqual(grouped['9'].length, 1);
assert.strictEqual(grouped['0'].length, 2);
assert.strictEqual(grouped['4'].length, 3);
assert.strictEqual(grouped['5'].length, 2);
assert.strictEqual(grouped['6'].length, 3);
assert.strictEqual(grouped['7'].length, 1);
assert.strictEqual(grouped['8'].length, 1);
assert.strictEqual(grouped['10'].length, 1);
});
});
Loading