fix(release): pack local AI runtime for release checks (#103941)

This commit is contained in:
Peter Steinberger
2026-07-10 21:59:59 +01:00
committed by GitHub
parent d395a8e6ec
commit b5f3fe900f
2 changed files with 78 additions and 18 deletions

View File

@@ -390,10 +390,11 @@ function runPackDry(): PackResult[] {
return JSON.parse(raw) as PackResult[];
}
function runPack(packDestination: string): PackResult[] {
function runPack(packDestination: string, cwd?: string): PackResult[] {
const raw = execPnpm(
["--config.ignore-scripts=true", "pack", "--json", "--pack-destination", packDestination],
{
cwd,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
maxBuffer: 1024 * 1024 * 100,
@@ -455,6 +456,25 @@ export function resolveReleaseCheckLocalPackageTarballs(
return tarballs;
}
export function prepareReleaseCheckLocalPackageTarballs(params: {
tmpRoot: string;
tarballDir?: string;
packLocalAi?: (packDestination: string) => PackResult[];
}): string[] {
if (params.tarballDir) {
return resolveReleaseCheckLocalPackageTarballs(params.tarballDir);
}
// The root tarball requires the exact sibling AI version. Never fall back to
// registry bytes, which could silently validate an older publication.
const packDir = join(params.tmpRoot, "ai-pack");
mkdirSync(packDir);
const packResults = params.packLocalAi
? params.packLocalAi(packDir)
: runPack(packDir, resolve("packages/ai"));
return [resolvePackedTarballPath(packDir, packResults)];
}
export function createPackedTarballInstallArgs(prefixDir: string): string[] {
return ["install", "--prefix", prefixDir, "--ignore-scripts", "--no-audit", "--no-fund"];
}
@@ -464,17 +484,16 @@ export function writePackedTarballInstallManifest(
tarballPath: string,
localPackageTarballs: string[],
): void {
if (localPackageTarballs.length > 1) {
const aiTarball = localPackageTarballs[0];
if (localPackageTarballs.length !== 1 || !aiTarball) {
throw new Error(
`release-check: packed install accepts at most one @openclaw/ai tarball; found ${localPackageTarballs.length}.`,
`release-check: packed install requires exactly one @openclaw/ai tarball; found ${localPackageTarballs.length}.`,
);
}
const dependencies: Record<string, string> = {
"@openclaw/ai": pathToFileURL(aiTarball).href,
openclaw: pathToFileURL(tarballPath).href,
};
if (localPackageTarballs[0]) {
dependencies["@openclaw/ai"] = pathToFileURL(localPackageTarballs[0]).href;
}
mkdirSync(prefixDir, { recursive: true });
writeFileSync(
join(prefixDir, "package.json"),
@@ -493,7 +512,7 @@ function installPackedTarball(
prefixDir: string,
tarballPath: string,
cwd: string,
localPackageTarballs: string[] = [],
localPackageTarballs: string[],
): void {
writePackedTarballInstallManifest(prefixDir, tarballPath, localPackageTarballs);
execNpm(createPackedTarballInstallArgs(prefixDir), {
@@ -921,7 +940,10 @@ function runPackedBundledChannelEntrySmoke(): void {
const packResults = runPack(packDir);
const tarballPath = resolvePackedTarballPath(packDir, packResults);
const prefixDir = join(tmpRoot, "prefix");
const localPackageTarballs = resolveReleaseCheckLocalPackageTarballs();
const localPackageTarballs = prepareReleaseCheckLocalPackageTarballs({
tmpRoot,
tarballDir: process.env[RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV],
});
installPackedTarball(prefixDir, tarballPath, tmpRoot, localPackageTarballs);
const packageRoot = join(prefixDir, "node_modules", "openclaw");

View File

@@ -5,6 +5,7 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
import {
createPackedTarballInstallArgs,
prepareReleaseCheckLocalPackageTarballs,
RELEASE_CHECK_LOCAL_PACKAGE_TARBALL_DIR_ENV,
resolveReleaseCheckLocalPackageTarballs,
writePackedTarballInstallManifest,
@@ -62,18 +63,55 @@ describe("release-check", () => {
}
});
it("preserves the no-env local release check path", () => {
it("packs the local AI workspace when no prepared tarball is supplied", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-ai-pack-test-"));
try {
const tarballs = prepareReleaseCheckLocalPackageTarballs({
tmpRoot: root,
packLocalAi: (packDestination) => {
const filename = "openclaw-ai-2026.7.1-beta.3.tgz";
writeFileSync(join(packDestination, filename), "fixture");
return [{ filename }];
},
});
expect(tarballs).toEqual([join(root, "ai-pack", "openclaw-ai-2026.7.1-beta.3.tgz")]);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("prefers the prepared AI tarball over packing the workspace", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-ai-pack-test-"));
try {
const preparedDir = join(root, "prepared");
mkdirSync(preparedDir);
const preparedTarball = join(preparedDir, "openclaw-ai-2026.7.1-beta.3.tgz");
writeFileSync(preparedTarball, "fixture");
const tarballs = prepareReleaseCheckLocalPackageTarballs({
tmpRoot: root,
tarballDir: preparedDir,
packLocalAi: () => {
throw new Error("workspace pack should not run");
},
});
expect(tarballs).toEqual([preparedTarball]);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("rejects a packed install without the local AI tarball", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-release-check-install-test-"));
try {
writePackedTarballInstallManifest(root, "/tmp/openclaw.tgz", []);
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
dependencies?: Record<string, string>;
private?: boolean;
};
expect(manifest.private).toBe(true);
expect(manifest.dependencies).toEqual({
openclaw: "file:///tmp/openclaw.tgz",
});
expect(() => writePackedTarballInstallManifest(root, "/tmp/openclaw.tgz", [])).toThrow(
"requires exactly one @openclaw/ai tarball",
);
expect(() =>
writePackedTarballInstallManifest(root, "/tmp/openclaw.tgz", [
"/tmp/openclaw-ai-one.tgz",
"/tmp/openclaw-ai-two.tgz",
]),
).toThrow("requires exactly one @openclaw/ai tarball");
} finally {
rmSync(root, { recursive: true, force: true });
}