Skip to content

Commit 5daf33a

Browse files
nanasessclaude
andcommitted
build: ESM へ移行し @actions/tool-cache を 4.x へ更新
@actions/tool-cache 4.0.0 および @actions/core/io/exec 3.x は pure ESM (package.json "type":"module"、exports に require エントリなし) のため、 バージョンを上げるにはプロジェクト全体を CommonJS から ESM へ移行する 必要がある。一連の移行をまとめて行う。 - package.json: "type":"module" 化、toolkit 依存を 4.x/3.x へ更新、 src/テストで未使用の @actions/github を削除、test を experimental-vm-modules で起動 - tsconfig: module/moduleResolution を NodeNext、isolatedModules を有効化 - src: 相対 import に .js 拡張子を付与。NodeNext は CJS パッケージの サブパスにも拡張子を要求するため typed-rest-client/HttpClient を 明示拡張子付きで import - jest: ESM 化 (jest.config.mjs + ts-jest useESM)。ESM では jest.mock の 巻き上げが効かないため、テストの jest.mock を jest.unstable_mockModule + 動的 import へ書き換え。equivalence は __dirname を import.meta.url 由来へ - lib/ dist/ を ESM で再生成。ncc 0.38.4 も type:module を見て ESM 出力し、 sourcemap-register を .cjs 化、dist/package.json を生成する download/extract から /usr/local/bin・C:\SeleniumWebDrivers への配置まで 実行パスの parity は不変。lib のレガシー shell/ps1 も従来どおり残置。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b875cab commit 5daf33a

30 files changed

Lines changed: 32299 additions & 59679 deletions

__tests__/chromedriver-api.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
* Run with: RUN_CHROMEDRIVER_API_TESTS=1 pnpm test __tests__/chromedriver-api.test.ts
66
*/
77

8+
import { jest } from "@jest/globals";
89
import {
910
buildLegacyLatestReleaseUrl,
1011
extractDriverUrlFromJson,
1112
ChromeKnownGoodVersions,
1213
ChromeVersion,
13-
} from "../src/chromedriver-helper";
14+
} from "../src/chromedriver-helper.js";
1415

1516
const JSON_URL =
1617
"https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json";

__tests__/chromedriver-helper.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
findFallbackVersion,
1212
getDefaultChromePath,
1313
getInstallPath,
14-
} from "../src/chromedriver-helper";
14+
} from "../src/chromedriver-helper.js";
1515

1616
// ---------------------------------------------------------------------------
1717
// Mock API response fixture

__tests__/equivalence.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import { execSync } from "child_process";
99
import * as path from "path";
10+
import { fileURLToPath } from "url";
1011
import {
1112
parseMajorVersion,
1213
parseVersion3,
@@ -19,7 +20,10 @@ import {
1920
getInstallPath,
2021
extractDriverUrlFromJson,
2122
findFallbackVersion,
22-
} from "../src/chromedriver-helper";
23+
} from "../src/chromedriver-helper.js";
24+
25+
// ESM has no __dirname; derive this file's directory from import.meta.url.
26+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
2327

2428
// ---------------------------------------------------------------------------
2529
// Run shell script and parse output

__tests__/installer-download.test.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@
22
* Unit tests for src/installer/download.ts.
33
*
44
* @actions/tool-cache is mocked so no real network or filesystem I/O occurs.
5+
*
6+
* The project is ESM, so the mock is registered with
7+
* `jest.unstable_mockModule` and both the mocked module and the module under
8+
* test are pulled in with dynamic `import()` *after* the mock is in place.
59
*/
610

7-
import * as tc from "@actions/tool-cache";
8-
import { downloadAndExtractZip } from "../src/installer/download";
11+
import { jest } from "@jest/globals";
12+
13+
jest.unstable_mockModule("@actions/tool-cache", () => ({
14+
downloadTool: jest.fn(),
15+
extractZip: jest.fn(),
16+
}));
917

10-
jest.mock("@actions/tool-cache");
18+
const tc = await import("@actions/tool-cache");
19+
const { downloadAndExtractZip } = await import("../src/installer/download.js");
1120

12-
const mockedTc = tc as jest.Mocked<typeof tc>;
21+
const mockedTc = jest.mocked(tc);
1322

1423
describe("downloadAndExtractZip", () => {
1524
beforeEach(() => {

__tests__/installer-http.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,21 @@
1313
* typed-rest-client is mocked so no real network I/O occurs.
1414
*/
1515

16+
import { jest } from "@jest/globals";
17+
1618
// Shared mock for the HttpClient instance method `get`. The module under test
1719
// constructs a single client at load time, so the constructor mock must return
18-
// an object backed by this shared mock.
19-
const mockGet = jest.fn();
20+
// an object backed by this shared mock. Declared before the mock factory so it
21+
// is captured by the closure when `await import()` evaluates http.ts.
22+
const mockGet = jest.fn() as jest.Mock<(url: string) => Promise<unknown>>;
2023

21-
jest.mock("typed-rest-client/HttpClient", () => ({
24+
jest.unstable_mockModule("typed-rest-client/HttpClient.js", () => ({
2225
HttpClient: jest.fn().mockImplementation(() => ({
2326
get: mockGet,
2427
})),
2528
}));
2629

27-
import { fetchText, fetchJson } from "../src/installer/http";
30+
const { fetchText, fetchJson } = await import("../src/installer/http.js");
2831

2932
/**
3033
* Build a fake typed-rest-client response with the given status code and body.
@@ -33,7 +36,7 @@ import { fetchText, fetchJson } from "../src/installer/http";
3336
function makeResponse(statusCode: number, body: string) {
3437
return {
3538
message: { statusCode },
36-
readBody: jest.fn().mockResolvedValue(body),
39+
readBody: jest.fn(() => Promise.resolve(body)),
3740
};
3841
}
3942

__tests__/installer-unix.test.ts

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,47 @@
1212
* - sudo present vs absent in the `mv` command
1313
*/
1414

15-
import * as exec from "@actions/exec";
16-
import * as io from "@actions/io";
15+
import { jest } from "@jest/globals";
1716
import * as path from "path";
18-
19-
import { installOnUnix } from "../src/installer/unix";
20-
import { downloadAndExtractZip as _downloadAndExtractZip } from "../src/installer/download";
21-
import {
22-
detectFullChromeVersion as _detectFullChromeVersion,
23-
resolveLegacyVersion as _resolveLegacyVersion,
24-
resolveModernDownload as _resolveModernDownload,
25-
} from "../src/installer/version";
26-
import { getInstallPath } from "../src/chromedriver-helper";
27-
28-
jest.mock("@actions/exec");
29-
jest.mock("@actions/io");
30-
jest.mock("@actions/core");
31-
jest.mock("../src/installer/download");
32-
jest.mock("../src/installer/version");
33-
34-
const execMock = exec.exec as jest.MockedFunction<typeof exec.exec>;
35-
const whichMock = io.which as jest.MockedFunction<typeof io.which>;
36-
const downloadAndExtractZip = _downloadAndExtractZip as jest.MockedFunction<
37-
typeof _downloadAndExtractZip
38-
>;
39-
const detectFullChromeVersion = _detectFullChromeVersion as jest.MockedFunction<
40-
typeof _detectFullChromeVersion
41-
>;
42-
const resolveLegacyVersion = _resolveLegacyVersion as jest.MockedFunction<
43-
typeof _resolveLegacyVersion
44-
>;
45-
const resolveModernDownload = _resolveModernDownload as jest.MockedFunction<
46-
typeof _resolveModernDownload
47-
>;
17+
import type { ExecOptions } from "@actions/exec";
18+
import { getInstallPath } from "../src/chromedriver-helper.js";
19+
20+
jest.unstable_mockModule("@actions/exec", () => ({
21+
exec: jest.fn(),
22+
}));
23+
jest.unstable_mockModule("@actions/io", () => ({
24+
which: jest.fn(),
25+
cp: jest.fn(),
26+
mkdirP: jest.fn(),
27+
mv: jest.fn(),
28+
}));
29+
jest.unstable_mockModule("@actions/core", () => ({
30+
addPath: jest.fn(),
31+
getInput: jest.fn(),
32+
info: jest.fn(),
33+
setFailed: jest.fn(),
34+
}));
35+
jest.unstable_mockModule("../src/installer/download.js", () => ({
36+
downloadAndExtractZip: jest.fn(),
37+
}));
38+
jest.unstable_mockModule("../src/installer/version.js", () => ({
39+
detectFullChromeVersion: jest.fn(),
40+
resolveLegacyVersion: jest.fn(),
41+
resolveModernDownload: jest.fn(),
42+
}));
43+
44+
const exec = await import("@actions/exec");
45+
const io = await import("@actions/io");
46+
const { installOnUnix } = await import("../src/installer/unix.js");
47+
const downloadMod = await import("../src/installer/download.js");
48+
const versionMod = await import("../src/installer/version.js");
49+
50+
const execMock = jest.mocked(exec.exec);
51+
const whichMock = jest.mocked(io.which);
52+
const downloadAndExtractZip = jest.mocked(downloadMod.downloadAndExtractZip);
53+
const detectFullChromeVersion = jest.mocked(versionMod.detectFullChromeVersion);
54+
const resolveLegacyVersion = jest.mocked(versionMod.resolveLegacyVersion);
55+
const resolveModernDownload = jest.mocked(versionMod.resolveModernDownload);
4856

4957
// The install path is derived from process.platform at runtime. Compute the
5058
// expected value the same way the implementation does so the test is valid
@@ -70,12 +78,12 @@ function setPresentCommands(present: string[]): void {
7078
function execCalls(): Array<{
7179
command: string;
7280
args: string[];
73-
options?: exec.ExecOptions;
81+
options?: ExecOptions;
7482
}> {
7583
return execMock.mock.calls.map((call) => ({
7684
command: call[0],
7785
args: (call[1] as string[]) ?? [],
78-
options: call[2] as exec.ExecOptions | undefined,
86+
options: call[2] as ExecOptions | undefined,
7987
}));
8088
}
8189

__tests__/installer-version.test.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1-
import * as exec from "@actions/exec";
2-
3-
import {
4-
detectFullChromeVersion,
5-
resolveLegacyVersion,
6-
resolveModernDownload,
7-
} from "../src/installer/version";
8-
import { fetchJson, fetchText } from "../src/installer/http";
9-
import { ChromeKnownGoodVersions } from "../src/chromedriver-helper";
10-
11-
jest.mock("@actions/exec");
12-
jest.mock("../src/installer/http");
13-
14-
const mockedExec = exec.exec as jest.MockedFunction<typeof exec.exec>;
15-
const mockedFetchJson = fetchJson as jest.MockedFunction<typeof fetchJson>;
16-
const mockedFetchText = fetchText as jest.MockedFunction<typeof fetchText>;
1+
import { jest } from "@jest/globals";
2+
import type { ExecOptions } from "@actions/exec";
3+
import type { ChromeKnownGoodVersions } from "../src/chromedriver-helper.js";
4+
5+
jest.unstable_mockModule("@actions/exec", () => ({
6+
exec: jest.fn(),
7+
}));
8+
jest.unstable_mockModule("../src/installer/http.js", () => ({
9+
fetchJson: jest.fn(),
10+
fetchText: jest.fn(),
11+
}));
12+
13+
const exec = await import("@actions/exec");
14+
const { detectFullChromeVersion, resolveLegacyVersion, resolveModernDownload } =
15+
await import("../src/installer/version.js");
16+
const { fetchJson, fetchText } = await import("../src/installer/http.js");
17+
18+
const mockedExec = jest.mocked(exec.exec);
19+
const mockedFetchJson = jest.mocked(fetchJson);
20+
const mockedFetchText = jest.mocked(fetchText);
1721

1822
// ---------------------------------------------------------------------------
1923
// Mock Chrome-for-Testing JSON fixture
@@ -59,7 +63,7 @@ const JSON_URL =
5963
*/
6064
function execWithStdout(output: string): typeof mockedExec {
6165
return mockedExec.mockImplementation(
62-
async (_cmd: string, _args?: string[], options?: exec.ExecOptions) => {
66+
async (_cmd: string, _args?: string[], options?: ExecOptions) => {
6367
options?.listeners?.stdout?.(Buffer.from(output));
6468
return 0;
6569
},
@@ -149,7 +153,7 @@ describe("detectFullChromeVersion", () => {
149153

150154
it("accumulates stdout emitted across multiple chunks", async () => {
151155
mockedExec.mockImplementation(
152-
async (_cmd: string, _args?: string[], options?: exec.ExecOptions) => {
156+
async (_cmd: string, _args?: string[], options?: ExecOptions) => {
153157
options?.listeners?.stdout?.(Buffer.from("Google Chrome "));
154158
options?.listeners?.stdout?.(Buffer.from("131.0.6778.204\n"));
155159
return 0;

__tests__/installer-windows.test.ts

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,40 @@
1313
* - the binary is installed to `C:\SeleniumWebDrivers\ChromeDriver`.
1414
*/
1515

16+
import { jest } from "@jest/globals";
1617
import * as path from "path";
1718

18-
import * as io from "@actions/io";
19-
import { installOnWindows } from "../src/installer/windows";
20-
import { downloadAndExtractZip } from "../src/installer/download";
21-
import {
22-
detectFullChromeVersion,
23-
resolveLegacyVersion,
24-
resolveModernDownload,
25-
} from "../src/installer/version";
26-
27-
jest.mock("@actions/io");
28-
jest.mock("../src/installer/download");
29-
jest.mock("../src/installer/version");
30-
31-
const mockedIo = io as jest.Mocked<typeof io>;
32-
const mockedDownloadAndExtractZip =
33-
downloadAndExtractZip as jest.MockedFunction<typeof downloadAndExtractZip>;
34-
const mockedDetectFullChromeVersion =
35-
detectFullChromeVersion as jest.MockedFunction<
36-
typeof detectFullChromeVersion
37-
>;
38-
const mockedResolveLegacyVersion = resolveLegacyVersion as jest.MockedFunction<
39-
typeof resolveLegacyVersion
40-
>;
41-
const mockedResolveModernDownload =
42-
resolveModernDownload as jest.MockedFunction<typeof resolveModernDownload>;
19+
jest.unstable_mockModule("@actions/io", () => ({
20+
which: jest.fn(),
21+
cp: jest.fn(),
22+
mkdirP: jest.fn(),
23+
mv: jest.fn(),
24+
}));
25+
jest.unstable_mockModule("../src/installer/download.js", () => ({
26+
downloadAndExtractZip: jest.fn(),
27+
}));
28+
jest.unstable_mockModule("../src/installer/version.js", () => ({
29+
detectFullChromeVersion: jest.fn(),
30+
resolveLegacyVersion: jest.fn(),
31+
resolveModernDownload: jest.fn(),
32+
}));
33+
34+
const io = await import("@actions/io");
35+
const { installOnWindows } = await import("../src/installer/windows.js");
36+
const downloadMod = await import("../src/installer/download.js");
37+
const versionMod = await import("../src/installer/version.js");
38+
39+
const mockedIo = jest.mocked(io);
40+
const mockedDownloadAndExtractZip = jest.mocked(
41+
downloadMod.downloadAndExtractZip,
42+
);
43+
const mockedDetectFullChromeVersion = jest.mocked(
44+
versionMod.detectFullChromeVersion,
45+
);
46+
const mockedResolveLegacyVersion = jest.mocked(versionMod.resolveLegacyVersion);
47+
const mockedResolveModernDownload = jest.mocked(
48+
versionMod.resolveModernDownload,
49+
);
4350

4451
const INSTALL_PATH = "C:\\SeleniumWebDrivers\\ChromeDriver";
4552
const DEFAULT_CHROME_PATH =

0 commit comments

Comments
 (0)