Skip to content
Merged
6 changes: 6 additions & 0 deletions packages/sea-builder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ project adheres to
permanent download cache (#72).
- Stopped `devPreinstall` from executing unpinned `pnpm dlx` packages
(#76).
- Made `--node`'s omitted default match its documentation: `sea-builder`
now actually resolves the latest patch of the oldest supported LTS
line when the option isn't passed, instead of silently embedding
whatever Node.js version happened to run the build, making SEA
builds reproducible across machines. The resolved version is now
also shown in the build output (#59).

## [0.21.0] - 2025-10-03

Expand Down
3 changes: 2 additions & 1 deletion packages/sea-builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ as `linux-x64` or `win32-x64`. `sea-builder` automatically invokes
`sea-builder` also accepts a `--node` option to choose the Node.js version.
Omitting this option uses the latest patch of the oldest supported LTS line.
Passing `--node=22` is equivalent to specifying `^22`, while
`--node=22.23` behaves like `~22.23`.
`--node=22.23` behaves like `~22.23`. The resolved version is shown in
the build output.

### Cache directory

Expand Down
4 changes: 2 additions & 2 deletions packages/sea-builder/src/listr2/createBuildTasks.mts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const createBuildTasks = async (
options: BuildTasksOptions,
): Promise<Listr> => {
const { arch, platform = process.platform, projectRoot } = options;
const { basename, download, execa, existsSync, mkdir, nodeVersion, targets } =
const { basename, download, execa, existsSync, mkdir, targets } =
await normalizeBuildOptions(options);
return new Listr([
createBuildTask(execa),
Expand All @@ -36,7 +36,7 @@ export const createBuildTasks = async (
download,
existsSync,
mkdir,
nodeVersion: await resolveNodeVersion(nodeVersion),
nodeVersion: await resolveNodeVersion(options.nodeVersion),
Comment thread
kurone-kito marked this conversation as resolved.
Comment thread
kurone-kito marked this conversation as resolved.
platform,
projectRoot,
targets,
Expand Down
66 changes: 66 additions & 0 deletions packages/sea-builder/src/listr2/createBuildTasks.spec.mts
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' }),
);
});
});
4 changes: 3 additions & 1 deletion packages/sea-builder/src/tasks/createCacheTask.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { Task } from './createTaskFactory.mjs';
* @returns Listr task object.
*/
export const createCacheTask = (opts: CacheOptions): Task => ({
title: 'Download the Node.js archives',
title: opts.nodeVersion
? `Download the Node.js archives (${opts.nodeVersion})`
: 'Download the Node.js archives',
task: () => createListrCacheTasks(opts).run(),
});
5 changes: 5 additions & 0 deletions packages/sea-builder/src/tasks/createCacheTask.spec.mts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ describe('createCacheTask', () => {

it('sets title', () =>
expect(createCacheTask(opts).title).toBe('Download the Node.js archives'));

it('includes the resolved Node.js version in the title when provided', () =>
expect(createCacheTask({ ...opts, nodeVersion: 'v22.23.2' }).title).toBe(
'Download the Node.js archives (v22.23.2)',
));
});
Loading