fix: keep private qa source only

This commit is contained in:
Gustavo Madeira Santana
2026-04-15 19:43:27 -04:00
parent 9b743d8768
commit dccb23e864
3 changed files with 136 additions and 7 deletions

View File

@@ -1,13 +1,75 @@
import { describe, expect, it } from "vitest";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { loadPrivateQaCliModule } from "./private-qa-cli.js";
describe("private-qa-cli", () => {
it("loads the private QA CLI facade through the local plugin-sdk entrypoint", async () => {
const module = await loadPrivateQaCliModule();
const tempDirs: string[] = [];
const originalPrivateQaCli = process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI;
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
if (originalPrivateQaCli === undefined) {
delete process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI;
} else {
process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI = originalPrivateQaCli;
}
});
it("loads the private QA CLI from a source checkout path", async () => {
process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI = "1";
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-private-qa-source-"));
tempDirs.push(repoRoot);
const importModule = vi.fn(async () => ({
isQaLabCliAvailable: expect.any(Function),
registerQaLabCli: expect.any(Function),
}));
const module = await loadPrivateQaCliModule({
importModule,
resolvePackageRootSync: () => repoRoot,
existsSync: (filePath) =>
[
path.join(repoRoot, ".git"),
path.join(repoRoot, "src"),
path.join(repoRoot, "dist", "plugin-sdk", "qa-lab.js"),
].includes(filePath),
});
expect(importModule).toHaveBeenCalledTimes(1);
expect(importModule.mock.calls[0]?.[0]).toContain("/dist/plugin-sdk/qa-lab.js");
expect(module).toMatchObject({
isQaLabCliAvailable: expect.any(Function),
registerQaLabCli: expect.any(Function),
});
});
it("rejects non-source package roots even when private QA is enabled", async () => {
process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI = "1";
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-private-qa-"));
tempDirs.push(root);
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "openclaw" }), "utf8");
const importModule = vi.fn(async () => ({}));
expect(() =>
loadPrivateQaCliModule({
resolvePackageRootSync: () => root,
importModule,
}),
).toThrow("Private QA CLI is only available from an OpenClaw source checkout.");
expect(importModule).not.toHaveBeenCalled();
});
it("rejects when the private QA env flag is disabled", async () => {
delete process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI;
const importModule = vi.fn(async () => ({}));
expect(() => loadPrivateQaCliModule({ importModule })).toThrow(
"Private QA CLI is only available from an OpenClaw source checkout.",
);
expect(importModule).not.toHaveBeenCalled();
});
});

View File

@@ -1,8 +1,65 @@
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { resolveOpenClawPackageRootSync } from "../../infra/openclaw-root.js";
const PRIVATE_QA_DIST_RELATIVE_PATH = path.join("dist", "plugin-sdk", "qa-lab.js");
export function isPrivateQaCliEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
return env.OPENCLAW_ENABLE_PRIVATE_QA_CLI === "1";
}
export function loadPrivateQaCliModule(): Promise<Record<string, unknown>> {
const specifier = "../../plugin-sdk/qa-lab.js";
return import(specifier) as Promise<Record<string, unknown>>;
function resolvePrivateQaSourceModuleSpecifier(params?: {
env?: NodeJS.ProcessEnv;
cwd?: string;
argv1?: string;
moduleUrl?: string;
resolvePackageRootSync?: typeof resolveOpenClawPackageRootSync;
existsSync?: typeof fs.existsSync;
}): string | null {
const env = params?.env ?? process.env;
if (!isPrivateQaCliEnabled(env)) {
return null;
}
const resolvePackageRootSync = params?.resolvePackageRootSync ?? resolveOpenClawPackageRootSync;
const packageRoot = resolvePackageRootSync({
argv1: params?.argv1 ?? process.argv[1],
cwd: params?.cwd ?? process.cwd(),
moduleUrl: params?.moduleUrl ?? import.meta.url,
});
if (!packageRoot) {
return null;
}
const existsSync = params?.existsSync ?? fs.existsSync;
const sourceModulePath = path.join(packageRoot, PRIVATE_QA_DIST_RELATIVE_PATH);
if (
!existsSync(path.join(packageRoot, ".git")) ||
!existsSync(path.join(packageRoot, "src")) ||
!existsSync(sourceModulePath)
) {
return null;
}
return pathToFileURL(sourceModulePath).href;
}
async function dynamicImportPrivateQaCliModule(
specifier: string,
): Promise<Record<string, unknown>> {
return (await import(specifier)) as Record<string, unknown>;
}
export function loadPrivateQaCliModule(params?: {
env?: NodeJS.ProcessEnv;
cwd?: string;
argv1?: string;
moduleUrl?: string;
resolvePackageRootSync?: typeof resolveOpenClawPackageRootSync;
existsSync?: typeof fs.existsSync;
importModule?: (specifier: string) => Promise<Record<string, unknown>>;
}): Promise<Record<string, unknown>> {
const specifier = resolvePrivateQaSourceModuleSpecifier(params);
if (!specifier) {
throw new Error("Private QA CLI is only available from an OpenClaw source checkout.");
}
return (params?.importModule ?? dynamicImportPrivateQaCliModule)(specifier);
}

View File

@@ -25,6 +25,9 @@ const { registerQaLabCli } = vi.hoisted(() => ({
qa.command("run").action(() => undefined);
}),
}));
const { loadPrivateQaCliModule } = vi.hoisted(() => ({
loadPrivateQaCliModule: vi.fn(async () => ({ registerQaLabCli })),
}));
const { inferAction, registerCapabilityCli } = vi.hoisted(() => {
const action = vi.fn();
@@ -37,7 +40,13 @@ const { inferAction, registerCapabilityCli } = vi.hoisted(() => {
vi.mock("../acp-cli.js", () => ({ registerAcpCli }));
vi.mock("../nodes-cli.js", () => ({ registerNodesCli }));
vi.mock("../capability-cli.js", () => ({ registerCapabilityCli }));
vi.mock("../../plugin-sdk/qa-lab.js", () => ({ registerQaLabCli }));
vi.mock("./private-qa-cli.js", async () => {
const actual = await vi.importActual<typeof import("./private-qa-cli.js")>("./private-qa-cli.js");
return {
...actual,
loadPrivateQaCliModule,
};
});
describe("registerSubCliCommands", () => {
const originalArgv = process.argv;
@@ -66,6 +75,7 @@ describe("registerSubCliCommands", () => {
registerNodesCli.mockClear();
nodesAction.mockClear();
registerQaLabCli.mockClear();
loadPrivateQaCliModule.mockClear();
registerCapabilityCli.mockClear();
inferAction.mockClear();
});