mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-16 16:11:38 +00:00
227 lines
6.9 KiB
TypeScript
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 });
|
|
}
|
|
});
|
|
});
|