FN-9046: fix scoped pnpm dependent selector
Correct workspace merge verification to test changed packages with their dependents. - Use pnpm's valid leading dependents selector for scoped merge tests. - Cover single, multiple, and shell-metacharacter package selectors. - Add a patch changeset for the verification fix. Files changed: .changeset/fn-9046-pnpm-filter-selector.md | 7 +++++ .../src/__tests__/merger-verification.test.ts | 33 +++++++++++++++++++--- .../src/merge/merger-workspace-test-commands.ts | 11 ++++++-- 3 files changed, 45 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-9046 Fusion-Task-Lineage: 07186296-36b7-42bb-aaa8-739dbf5e00d5 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-9046-pnpm-filter-selector.md
Normal file
7
.changeset/fn-9046-pnpm-filter-selector.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Merge verification now runs tests for packages depending on a changed package.
|
||||
category: fix
|
||||
dev: `deriveScopedPnpmTestCommand` now uses `...<pkg>` instead of malformed `<pkg>...^` selectors.
|
||||
@@ -3000,7 +3000,8 @@ describe("inferDefaultTestCommand — pnpm workspace scoping", () => {
|
||||
});
|
||||
|
||||
const result = inferDefaultTestCommand("/tmp/root", undefined, undefined, "main", "fusion/fn-123");
|
||||
expect(result?.command).toBe(`pnpm --filter '@fusion/dashboard...^' test`);
|
||||
expect(result?.command).toBe(`pnpm --filter '...@fusion/dashboard' test`);
|
||||
expect(result?.command).not.toMatch(/\.\.\.\^/);
|
||||
expect(result?.testSource).toBe("inferred-scoped");
|
||||
expect(mockedExecSync).toHaveBeenCalledWith(
|
||||
"git diff --name-only 'main'...'fusion/fn-123'",
|
||||
@@ -3036,12 +3037,36 @@ describe("inferDefaultTestCommand — pnpm workspace scoping", () => {
|
||||
});
|
||||
|
||||
const result = inferDefaultTestCommand("/tmp/root", undefined, undefined, "main", "fusion/fn-123");
|
||||
expect(result?.command).toContain("--filter");
|
||||
expect(result?.command).toContain("@fusion/dashboard");
|
||||
expect(result?.command).toContain("@fusion/engine");
|
||||
expect(result?.command).toBe(
|
||||
"pnpm --filter '...@fusion/dashboard' --filter '...@fusion/engine' test",
|
||||
);
|
||||
expect(result?.command).not.toMatch(/\.\.\.\^/);
|
||||
expect(result?.testSource).toBe("inferred-scoped");
|
||||
});
|
||||
|
||||
it("keeps a metacharacter package name shell-quoted inside a dependents selector", () => {
|
||||
mockedExistsSync.mockImplementation((p: any) => {
|
||||
const path = String(p);
|
||||
return path.includes("pnpm-lock.yaml") || path.includes("pnpm-workspace.yaml") || path.includes("package.json");
|
||||
});
|
||||
mockedReadFileSync.mockImplementation((p: any) => {
|
||||
const path = String(p);
|
||||
if (path.includes("pnpm-workspace.yaml")) return `packages:\n - "packages/*"\n`;
|
||||
if (path.includes("dashboard/package.json")) return JSON.stringify({ name: "@evil/pkg'; rm -rf /" });
|
||||
return JSON.stringify({ name: "unknown" });
|
||||
});
|
||||
mockedReaddirSync.mockReturnValue([
|
||||
{ name: "dashboard", isDirectory: () => true },
|
||||
] as any);
|
||||
mockedExecSync.mockImplementation((cmd: any) => String(cmd).includes("git diff --name-only")
|
||||
? "packages/dashboard/src/index.ts\n"
|
||||
: "");
|
||||
|
||||
const result = inferDefaultTestCommand("/tmp/root", undefined, undefined, "main", "fusion/fn-123");
|
||||
expect(result?.command).toBe("pnpm --filter '...@evil/pkg'\\''; rm -rf /' test");
|
||||
expect(result?.command).not.toMatch(/\.\.\.\^/);
|
||||
});
|
||||
|
||||
it("falls back to pnpm test (inferred) when all changes are root-only files", () => {
|
||||
mockedExistsSync.mockImplementation((p: any) => {
|
||||
const path = String(p);
|
||||
|
||||
@@ -228,11 +228,18 @@ export function deriveScopedPnpmTestCommand(rootDir: string, baseBranch: string,
|
||||
}
|
||||
|
||||
// 5. Compose the scoped pnpm command
|
||||
// `...^` includes dependents (packages that import the changed packages).
|
||||
/*
|
||||
* FNXC:Verification 2026-08-15-04:18:
|
||||
* pnpm `name...` selects a package with dependencies, `...name` selects it
|
||||
* with dependents, and `...^name` excludes the package from its dependents.
|
||||
* Merge verification needs `...name` so changed-package tests run alongside
|
||||
* dependent tests; the former malformed trailing `name...^` could produce a
|
||||
* zero-package, zero-test green verification.
|
||||
*/
|
||||
// Package names come from workspace package.json files (potentially
|
||||
// untrusted) so we quote each filter argument via `quoteArg` to prevent
|
||||
// shell interpolation if a name contains metacharacters.
|
||||
const filters = packageNames.map((name) => `--filter ${quoteArg(`${name}...^`)}`).join(" ");
|
||||
const filters = packageNames.map((name) => `--filter ${quoteArg(`...${name}`)}`).join(" ");
|
||||
return `pnpm ${filters} test`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user