diff --git a/src/application/ReferenceSourceVcsAdapter.ts b/src/application/ReferenceSourceVcsAdapter.ts index 42ab4e7388..3ddad25d71 100644 --- a/src/application/ReferenceSourceVcsAdapter.ts +++ b/src/application/ReferenceSourceVcsAdapter.ts @@ -2,8 +2,6 @@ import fs from "node:fs"; import { lstat, readFile } from "node:fs/promises"; import { join, resolve } from "node:path"; -import { resolveRef } from "isomorphic-git"; - export type ReferenceSourceVcsInfo = | { readonly kind: "git"; @@ -42,6 +40,8 @@ export const readReferenceSourceVcs = async ( }; const resolveSourceHead = async (root: string): Promise => { + // isomorphic-git is loaded on first use so CLI and MCP startup skip it. + const { resolveRef } = await import("isomorphic-git"); const dotgit = join(root, ".git"); if (!(await lstat(dotgit)).isFile()) return resolveRef({ fs, dir: root, ref: "HEAD" }); diff --git a/src/cliBrowserScenarioCommands.ts b/src/cliBrowserScenarioCommands.ts index f98e415cb2..a1a1f86e15 100644 --- a/src/cliBrowserScenarioCommands.ts +++ b/src/cliBrowserScenarioCommands.ts @@ -2,7 +2,6 @@ import { Cli } from "incur"; import { z } from "zod"; import { captureBrowserScenario } from "./application/BrowserScenarioCaptureService.js"; -import { createBrowserScenarioProvider } from "./composition/browserScenario.js"; import { CLI_COMMANDS } from "./cliCommandNames.js"; import { parseCliJsonInput, resolveCliJsonPaths } from "./cliJsonInput.js"; import { logCliCommand } from "./cliLogging.js"; @@ -44,6 +43,9 @@ export const registerBrowserScenarioCommands = ( projectInputIssues(scenario.error.issues, input.value), ), ); + // Load Playwright only for this command, not during CLI startup. + const { createBrowserScenarioProvider } = + await import("./composition/browserScenario.js"); const result = await captureBrowserScenario( createBrowserScenarioProvider(environment), scenario.data, diff --git a/tests/boundary/cli/cliStartupImports.test.ts b/tests/boundary/cli/cliStartupImports.test.ts new file mode 100644 index 0000000000..c084b62122 --- /dev/null +++ b/tests/boundary/cli/cliStartupImports.test.ts @@ -0,0 +1,76 @@ +import { execFile } from "node:child_process"; +import { mkdir } from "node:fs/promises"; +import { join } from "node:path"; +import { promisify } from "node:util"; + +import { describe, expect, it } from "vitest"; + +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; + +const execute = promisify(execFile); +const MARKER = "REA_TEST_RESOLVED "; +// Dependencies that only one command uses; startup must not resolve them. +const DEFERRED_PACKAGES = ["playwright-core", "isomorphic-git"] as const; +// Report each resolved deferred package on stderr without changing resolution. +const RESOLUTION_HOOK = `data:text/javascript,${encodeURIComponent(` +import { registerHooks } from "node:module"; +const packages = ${JSON.stringify(DEFERRED_PACKAGES)}; +registerHooks({ + resolve(specifier, context, next) { + const resolved = next(specifier, context); + const name = packages.find((item) => + resolved.url.includes("/node_modules/" + item + "/"), + ); + if (name !== undefined) + process.stderr.write(${JSON.stringify(MARKER)} + name + "\\n"); + return resolved; + }, +}); +`)}`; + +const resolvedPackages = async ( + arguments_: readonly string[], +): Promise> => { + const { stderr } = await execute( + process.execPath, + [`--import=${RESOLUTION_HOOK}`, ...arguments_], + { cwd: process.cwd(), maxBuffer: 16 * 1_024 * 1_024 }, + ); + return new Set( + stderr + .split("\n") + .filter((line) => line.startsWith(MARKER)) + .map((line) => line.slice(MARKER.length)), + ); +}; + +const evaluate = (source: string): readonly string[] => [ + "--input-type=module", + "--eval", + source, +]; + +describe("CLI startup imports", () => { + it("detects each deferred package when its command code loads", async () => { + await expect( + resolvedPackages( + evaluate('await import("./dist/composition/browserScenario.js");'), + ), + ).resolves.toContain("playwright-core"); + const source = await createTestTempDirectory("rea-startup-imports-"); + await mkdir(join(source, ".git")); + await expect( + resolvedPackages( + evaluate( + `const { readReferenceSourceVcs } = await import("./dist/application/ReferenceSourceVcsAdapter.js"); await readReferenceSourceVcs(${JSON.stringify(source)});`, + ), + ), + ).resolves.toContain("isomorphic-git"); + }); + + it("does not load command-specific packages to register commands", async () => { + await expect( + resolvedPackages(["scripts/rea.mjs", "--help"]), + ).resolves.toEqual(new Set()); + }); +});