FN-6102: guard spawnSync lookup in pi extensions
Avoid crashing when partial child_process mocks omit spawnSync. - lazily load spawnSync from node:child_process via createRequire - return null from Git linked worktree detection when spawnSync is unavailable - add a regression test covering partial child_process mocks that only stub execSync Files changed: packages/core/src/__tests__/pi-extensions.test.ts | 22 +++++++++++++++++++++- packages/core/src/pi-extensions.ts | 18 +++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-6102 Fusion-Task-Lineage: 22f98a1c-d296-4aa4-ac3d-7beead127dac
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
@@ -38,6 +38,26 @@ describe("getProjectRootFromWorktree", () => {
|
||||
).toBe("/tmp");
|
||||
});
|
||||
|
||||
it("returns null without throwing when child_process partial mocks omit spawnSync", async () => {
|
||||
vi.resetModules();
|
||||
vi.doMock("node:child_process", () => ({
|
||||
execSync: vi.fn(),
|
||||
}));
|
||||
|
||||
try {
|
||||
const { getProjectRootFromWorktree: getProjectRootFromWorktreeWithPartialMock } = await import(
|
||||
"../pi-extensions.js"
|
||||
);
|
||||
const unmatchedPath = join(tmpdir(), "fn-6102-not-a-worktree");
|
||||
|
||||
expect(() => getProjectRootFromWorktreeWithPartialMock(unmatchedPath)).not.toThrow();
|
||||
expect(getProjectRootFromWorktreeWithPartialMock(unmatchedPath)).toBe(null);
|
||||
} finally {
|
||||
vi.doUnmock("node:child_process");
|
||||
vi.resetModules();
|
||||
}
|
||||
});
|
||||
|
||||
it("detects arbitrary Git linked worktree paths when the parent has Fusion metadata", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "fn-6079-root-"));
|
||||
const worktreeRoot = mkdtempSync(join(tmpdir(), "fusion-ai-merge-fn-6079-"));
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { basename, dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const FUSION_DISABLED_EXTENSIONS_KEY = "fusionDisabledExtensions";
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
let cachedSpawnSync: typeof import("node:child_process")["spawnSync"] | undefined;
|
||||
let didLoadSpawnSync = false;
|
||||
function getSpawnSync(): typeof import("node:child_process")["spawnSync"] | undefined {
|
||||
if (!didLoadSpawnSync) {
|
||||
didLoadSpawnSync = true;
|
||||
cachedSpawnSync = require("node:child_process").spawnSync;
|
||||
}
|
||||
return cachedSpawnSync;
|
||||
}
|
||||
|
||||
export type PiExtensionSource = "fusion-global" | "pi-global" | "fusion-project" | "pi-project" | "package";
|
||||
|
||||
@@ -76,6 +87,11 @@ export function getProjectRootFromWorktree(
|
||||
}
|
||||
|
||||
function getProjectRootFromGitLinkedWorktree(cwd: string): string | null {
|
||||
const spawnSync = getSpawnSync();
|
||||
if (!spawnSync) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const resolvedCwd = resolve(cwd);
|
||||
const commonDir = spawnSync("git", ["rev-parse", "--git-common-dir"], {
|
||||
cwd: resolvedCwd,
|
||||
|
||||
Reference in New Issue
Block a user