-
Notifications
You must be signed in to change notification settings - Fork 0
fix(sea-builder): make --node's omitted default match its docs #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
kurone-kito
merged 9 commits into
main
from
issue/59-sea-builder-node-default-contradicts-its
Aug 11, 2026
Merged
Changes from 7 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
824746e
fix(sea-builder): make --node's omitted default match its docs
kurone-kito 952a7ac
fix(sea-builder): bound the default LTS to its supported window
kurone-kito 09be49c
fix(sea-builder): fix JSDoc drift and freeze the test clock
kurone-kito a7be2a5
fix(sea-builder): fix off-by-one EOL date and skip unneeded work
kurone-kito e53cb52
chore(idd): trust the source-pinned advisory-convergence check
kurone-kito 4b28d37
docs(idd): stop implying issue 51 itself is closed
kurone-kito fc538fe
fix(sea-builder): fail closed when no LTS is in its support window
kurone-kito 15b2ed7
docs(idd): document the source-pinning trust limitation
kurone-kito 2825293
fix(sea-builder): fix createListrCacheTasks' return JSDoc
kurone-kito File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { beforeEach, describe, expect, it, vi } from 'vitest'; | ||
| import { createBuildTasks } from './createBuildTasks.mjs'; | ||
|
|
||
| const mocks = vi.hoisted(() => ({ | ||
| createBuildTask: vi.fn(() => ({ task: vi.fn(), title: 'Build' })), | ||
| createCacheTask: vi.fn(() => ({ task: vi.fn(), title: 'Cache' })), | ||
| createSeaTask: vi.fn(() => ({ task: vi.fn(), title: 'Sea' })), | ||
| normalizeBuildOptions: vi.fn(), | ||
| resolveNodeVersion: vi.fn(), | ||
| })); | ||
|
|
||
| vi.mock('../tasks/createBuildTask.mjs', () => ({ | ||
| createBuildTask: mocks.createBuildTask, | ||
| })); | ||
|
|
||
| vi.mock('../tasks/createCacheTask.mjs', () => ({ | ||
| createCacheTask: mocks.createCacheTask, | ||
| })); | ||
|
|
||
| vi.mock('../tasks/createSeaTask.mjs', () => ({ | ||
| createSeaTask: mocks.createSeaTask, | ||
| })); | ||
|
|
||
| vi.mock('../tasks/normalizeBuildOptions.mjs', () => ({ | ||
| normalizeBuildOptions: mocks.normalizeBuildOptions, | ||
| })); | ||
|
|
||
| vi.mock('../utils/resolveNodeVersion.mjs', () => ({ | ||
| resolveNodeVersion: mocks.resolveNodeVersion, | ||
| })); | ||
|
|
||
| describe('createBuildTasks', () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| mocks.normalizeBuildOptions.mockResolvedValue({ | ||
| basename: 'foo', | ||
| download: vi.fn(), | ||
| execa: vi.fn(), | ||
| existsSync: vi.fn(), | ||
| mkdir: vi.fn(), | ||
| // A pre-defaulted value, distinct from the raw option, so a | ||
| // regression that resolves this instead of the raw option is | ||
| // caught by the assertions below. | ||
| nodeVersion: 'v20.19.5', | ||
| targets: ['linux-x64'], | ||
| }); | ||
| mocks.resolveNodeVersion.mockResolvedValue('v22.23.2'); | ||
| }); | ||
|
|
||
| it('resolves the node version from the raw option, not the pre-defaulted one', async () => { | ||
| await createBuildTasks({ basename: 'foo' }); | ||
| expect(mocks.resolveNodeVersion).toHaveBeenCalledWith(undefined); | ||
| }); | ||
|
|
||
| it('passes an explicit --node spec through untouched', async () => { | ||
| await createBuildTasks({ basename: 'foo', nodeVersion: '20' }); | ||
| expect(mocks.resolveNodeVersion).toHaveBeenCalledWith('20'); | ||
| }); | ||
|
|
||
| it('passes the resolved node version to createCacheTask', async () => { | ||
| await createBuildTasks({ basename: 'foo' }); | ||
| expect(mocks.createCacheTask).toHaveBeenCalledWith( | ||
| expect.objectContaining({ nodeVersion: 'v22.23.2' }), | ||
| ); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import type { MajorNodeVersion } from 'all-node-versions'; | ||
| import releaseSchedule from 'node-releases/data/release-schedule/release-schedule.json' with { | ||
| type: 'json', | ||
| }; | ||
|
|
||
| /** A single major's entry in node-releases' release schedule data. */ | ||
| interface ScheduleEntry { | ||
| /** ISO date the major's support window ends, if scheduled. */ | ||
| readonly end?: string; | ||
|
|
||
| /** ISO date the major entered LTS, if it ever did. */ | ||
| readonly lts?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Filter out majors whose LTS support window has already ended, per | ||
| * `node-releases`' release schedule. `all-node-versions`' own `lts` flag | ||
| * marks every major that *ever* had an LTS codename, going back to Node 4 | ||
| * — this narrows that down to the ones still within their scheduled | ||
| * support window. | ||
| * @param majors Majors to filter. | ||
| * @param now Current time, used to determine end-of-life status. | ||
| * @returns Majors that are LTS and not yet past their scheduled `end` date. | ||
| */ | ||
| export const filterSupportedLts = < | ||
| T extends Pick<MajorNodeVersion, 'lts' | 'major'>, | ||
| >( | ||
| majors: readonly T[], | ||
| now: Date = new Date(), | ||
| ): readonly T[] => | ||
| majors.filter(({ lts, major }) => { | ||
| if (!lts) { | ||
| return false; | ||
| } | ||
| const entry = (releaseSchedule as Record<string, ScheduleEntry>)[ | ||
| `v${major}` | ||
| ]; | ||
| if (!entry?.lts || !entry.end) { | ||
| return false; | ||
| } | ||
| // `entry.end` is a date-only string (e.g. "2026-04-30"), which | ||
| // Date parses as that day's UTC midnight; add a day so the entire | ||
| // end date itself still counts as supported. | ||
| const endOfSupportWindow = new Date(entry.end).getTime() + 86_400_000; | ||
| return endOfSupportWindow > now.getTime(); | ||
| }); |
61 changes: 61 additions & 0 deletions
61
packages/sea-builder/src/utils/filterSupportedLts.spec.mts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { describe, expect, it } from 'vitest'; | ||
| import { filterSupportedLts } from './filterSupportedLts.mjs'; | ||
|
|
||
| const majors = [ | ||
| { major: 26, latest: '26.0.0' }, | ||
| { major: 25, latest: '25.9.0' }, | ||
| { major: 24, latest: '24.18.1', lts: 'krypton' }, | ||
| { major: 23, latest: '23.11.1' }, | ||
| { major: 22, latest: '22.23.2', lts: 'jod' }, | ||
| { major: 20, latest: '20.20.2', lts: 'iron' }, | ||
| { major: 18, latest: '18.20.8', lts: 'hydrogen' }, | ||
| { major: 4, latest: '4.9.1', lts: 'argon' }, | ||
| ] as const; | ||
|
|
||
| describe('filterSupportedLts', () => { | ||
| it('excludes non-LTS majors', () => { | ||
| const now = new Date('2026-08-11'); | ||
| expect(filterSupportedLts(majors, now)).not.toContainEqual( | ||
| expect.objectContaining({ major: 26 }), | ||
| ); | ||
| }); | ||
|
|
||
| it('excludes an LTS major whose support window has already ended', () => { | ||
| // Node 20 (Iron) ends 2026-04-30; Node 18 (Hydrogen) ends 2025-04-30. | ||
| const now = new Date('2026-08-11'); | ||
| const result = filterSupportedLts(majors, now); | ||
| expect(result).not.toContainEqual(expect.objectContaining({ major: 20 })); | ||
| expect(result).not.toContainEqual(expect.objectContaining({ major: 18 })); | ||
| }); | ||
|
|
||
| it('excludes a long-retired LTS major', () => { | ||
| const now = new Date('2026-08-11'); | ||
| expect(filterSupportedLts(majors, now)).not.toContainEqual( | ||
| expect.objectContaining({ major: 4 }), | ||
| ); | ||
| }); | ||
|
|
||
| it('keeps LTS majors still within their support window', () => { | ||
| const now = new Date('2026-08-11'); | ||
| const result = filterSupportedLts(majors, now); | ||
| expect(result).toContainEqual(expect.objectContaining({ major: 24 })); | ||
| expect(result).toContainEqual(expect.objectContaining({ major: 22 })); | ||
| }); | ||
|
|
||
| it('still counts the scheduled end date itself as supported', () => { | ||
| // Node 20 (Iron) ends 2026-04-30; a date-only string parses as that | ||
| // day's UTC midnight, so a naive `> now` comparison would treat the | ||
| // entire end date as already unsupported. | ||
| const stillOnEndDate = new Date('2026-04-30T18:00:00.000Z'); | ||
| expect(filterSupportedLts(majors, stillOnEndDate)).toContainEqual( | ||
| expect.objectContaining({ major: 20 }), | ||
| ); | ||
| }); | ||
|
|
||
| it('excludes it starting the day after the scheduled end date', () => { | ||
| const dayAfterEnd = new Date('2026-05-01T00:00:00.001Z'); | ||
| expect(filterSupportedLts(majors, dayAfterEnd)).not.toContainEqual( | ||
| expect.objectContaining({ major: 20 }), | ||
| ); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.