Files
openclaw/test/scripts/ocm-npm-workspace-deps.test.ts
2026-07-11 19:57:54 +08:00

227 lines
6.9 KiB
TypeScript

import { execFileSync } from "node:child_process";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { delimiter, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import {
buildInstallManifest,
parseWorkspaceDependencyDirs,
resolveNpmEnvironment,
resolveRuntimePackEnvironment,
resolveRuntimePackPlan,
resolveWorkspaceInstallPlan,
rewriteWorkspaceDependencyVersions,
} from "../../scripts/ocm-npm-workspace-deps.mjs";
const adapterPath = fileURLToPath(
new URL("../../scripts/ocm-npm-workspace-deps.mjs", import.meta.url),
);
describe("OCM npm workspace dependency adapter", () => {
it("allows Unreleased notes only for non-publishing pack commands", () => {
const env = { KEEP: "value" };
expect(resolveNpmEnvironment(["install"], env)).toBe(env);
expect(resolveNpmEnvironment(["pack", "--silent"], env)).toEqual({
KEEP: "value",
OPENCLAW_PREPACK_ALLOW_UNRELEASED_CHANGELOG: "1",
});
});
it("uses a prepared runtime-only pack for the diagnostic build profile", () => {
expect(
resolveRuntimePackPlan(["pack", "--pack-destination", "/tmp/out"], {
OPENCLAW_OCM_RUNTIME_BUILD_PROFILE: "sourcePerformance",
}),
).toEqual({
profile: "sourcePerformance",
packArgs: ["pack", "--pack-destination", "/tmp/out", "--ignore-scripts"],
});
});
it("keeps normal package builds on the full prepack path", () => {
expect(resolveRuntimePackPlan(["pack"], {})).toBeNull();
expect(
resolveRuntimePackPlan(["install"], {
OPENCLAW_OCM_RUNTIME_BUILD_PROFILE: "sourcePerformance",
}),
).toBeNull();
});
it("rejects unsupported runtime build profiles", () => {
expect(() =>
resolveRuntimePackPlan(["pack"], {
OPENCLAW_OCM_RUNTIME_BUILD_PROFILE: "qaRuntime",
}),
).toThrow("invalid OPENCLAW_OCM_RUNTIME_BUILD_PROFILE: qaRuntime");
});
it("pins one timestamp and commit across the prepared runtime pack", () => {
const env = resolveRuntimePackEnvironment(
{ KEEP: "value" },
() => new Date("2026-07-11T12:34:56.000Z"),
() => "ABCDEF0123456789ABCDEF0123456789ABCDEF01",
);
expect(env).toMatchObject({
KEEP: "value",
GIT_COMMIT: "abcdef0123456789abcdef0123456789abcdef01",
OPENCLAW_BUILD_TIMESTAMP: "2026-07-11T12:34:56.000Z",
});
});
it("rejects ambiguous runtime pack commits", () => {
expect(() =>
resolveRuntimePackEnvironment(
{ GITHUB_SHA: "abc123" },
() => new Date("2026-07-11T12:34:56.000Z"),
() => null,
),
).toThrow("runtime pack commit must be a full 40-character hexadecimal SHA");
});
it("resolves workspace package directories", () => {
expect(
parseWorkspaceDependencyDirs(["packages/ai", "extensions/example"].join(delimiter), "/repo"),
).toEqual(["/repo/packages/ai", "/repo/extensions/example"]);
});
it("replaces the root archive argument with a prepared install manifest", () => {
expect(
resolveWorkspaceInstallPlan(
[
"install",
"--prefix",
"runtime",
"--omit=dev",
"--no-save",
"--package-lock=false",
"openclaw.tgz",
],
["/repo/packages/ai"],
"/repo",
),
).toEqual({
installArgs: [
"install",
"--prefix",
"runtime",
"--omit=dev",
"--no-save",
"--package-lock=false",
],
prefixDir: "/repo/runtime",
rootArchive: "/repo/openclaw.tgz",
});
});
it("keeps normal npm commands unchanged", () => {
expect(resolveWorkspaceInstallPlan(["pack", "--silent"], ["/repo/packages/ai"])).toBeNull();
expect(resolveWorkspaceInstallPlan(["install", "openclaw.tgz"], [])).toBeNull();
});
it("builds a manifest with the root and local workspace tarballs", () => {
expect(
buildInstallManifest("/tmp/openclaw.tgz", [
{ name: "@openclaw/ai", tarball: "/tmp/openclaw-ai.tgz" },
]),
).toEqual({
private: true,
dependencies: {
"@openclaw/ai": "file:///tmp/openclaw-ai.tgz",
openclaw: "file:///tmp/openclaw.tgz",
},
});
});
it("rewrites packed workspace protocols to the local package version", () => {
const packageJson = {
dependencies: {
"@openclaw/ai": "workspace:*",
chalk: "5.6.2",
},
};
expect(
rewriteWorkspaceDependencyVersions(packageJson, [
{
name: "@openclaw/ai",
version: "2026.7.1-beta.3",
tarball: "/tmp/openclaw-ai.tgz",
},
]),
).toBe(1);
expect(packageJson.dependencies).toEqual({
"@openclaw/ai": "2026.7.1-beta.3",
chalk: "5.6.2",
});
});
it("installs a packed root with a local workspace dependency", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-ocm-adapter-test-"));
try {
const archiveRoot = join(root, "archive");
const packagedRoot = join(archiveRoot, "package");
const workspaceDir = join(root, "ai");
const installDir = join(root, "install");
const rootArchive = join(root, "openclaw.tgz");
mkdirSync(packagedRoot, { recursive: true });
mkdirSync(workspaceDir, { recursive: true });
writeFileSync(
join(packagedRoot, "package.json"),
`${JSON.stringify({
name: "openclaw",
version: "1.0.0",
dependencies: { "@openclaw/ai": "workspace:*" },
})}\n`,
);
writeFileSync(
join(workspaceDir, "package.json"),
`${JSON.stringify({
name: "@openclaw/ai",
version: "1.0.0",
main: "index.js",
})}\n`,
);
writeFileSync(join(workspaceDir, "index.js"), "export const ready = true;\n");
execFileSync("tar", ["-czf", rootArchive, "-C", archiveRoot, "package"]);
execFileSync(
process.execPath,
[
adapterPath,
"install",
"--prefix",
installDir,
"--omit=dev",
"--no-save",
"--package-lock=false",
rootArchive,
],
{
env: {
...process.env,
OPENCLAW_OCM_REAL_NPM_BIN: process.platform === "win32" ? "npm.cmd" : "npm",
OPENCLAW_OCM_WORKSPACE_DEPENDENCY_DIRS: workspaceDir,
npm_config_audit: "false",
npm_config_cache: join(root, "npm-cache"),
npm_config_fund: "false",
},
stdio: "pipe",
},
);
expect(
JSON.parse(readFileSync(join(installDir, "node_modules/openclaw/package.json"), "utf8"))
.version,
).toBe("1.0.0");
expect(
JSON.parse(readFileSync(join(installDir, "node_modules/@openclaw/ai/package.json"), "utf8"))
.version,
).toBe("1.0.0");
} finally {
rmSync(root, { force: true, recursive: true });
}
});
});