fix(release): package legacy candidates without AI workspace (#100469)

This commit is contained in:
Vincent Koc
2026-07-05 15:19:00 -07:00
committed by GitHub
parent c40f5e0683
commit c4a862ab53
2 changed files with 81 additions and 1 deletions
+19 -1
View File
@@ -435,6 +435,7 @@ export async function prepareBundledAiRuntimePackage(
options = {},
) {
const packageJsonPath = path.join(sourceDir, "package.json");
const aiRuntimePackageJsonPath = path.join(sourceDir, "packages", "ai", "package.json");
const aiRuntimePath = path.join(sourceDir, "node_modules", "@openclaw", "ai");
const aiRuntimeBackupPath = path.join(
sourceDir,
@@ -453,7 +454,24 @@ export async function prepareBundledAiRuntimePackage(
} catch (error) {
throw new Error(`failed to parse ${packageJsonPath}`, { cause: error });
}
if (typeof packageJson.dependencies?.[AI_RUNTIME_PACKAGE] !== "string") {
const aiRuntimeDependency = packageJson.dependencies?.[AI_RUNTIME_PACKAGE];
let hasAiRuntimeWorkspace = false;
try {
await fs.access(aiRuntimePackageJsonPath);
hasAiRuntimeWorkspace = true;
} catch (error) {
if (error?.code !== "ENOENT") {
throw error;
}
}
// Release checks can package refs from before the AI runtime was split into a workspace package.
if (!hasAiRuntimeWorkspace && aiRuntimeDependency === undefined) {
return async () => {};
}
if (!hasAiRuntimeWorkspace) {
throw new Error("@openclaw/ai dependency requires the packages/ai workspace");
}
if (typeof aiRuntimeDependency !== "string") {
throw new Error("root package.json must declare @openclaw/ai as a dependency");
}
@@ -234,6 +234,7 @@ describe("package-openclaw-for-docker", () => {
)}\n`;
const installedAiPath = path.join(sourceDir, "node_modules", "@openclaw", "ai");
fs.mkdirSync(path.join(sourceDir, "packages", "ai"), { recursive: true });
fs.writeFileSync(path.join(sourceDir, "packages", "ai", "package.json"), "{}\n");
fs.mkdirSync(installedAiPath, { recursive: true });
fs.writeFileSync(path.join(installedAiPath, "original-marker"), "workspace package");
fs.writeFileSync(packageJsonPath, originalPackageJson);
@@ -291,6 +292,67 @@ describe("package-openclaw-for-docker", () => {
}
});
it("leaves pre-AI-workspace package sources unchanged", async () => {
const sourceDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docker-legacy-source-"));
const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docker-legacy-output-"));
const packageJsonPath = path.join(sourceDir, "package.json");
const originalPackageJson = `${JSON.stringify({
dependencies: { "dep-a": "1.2.3" },
name: "openclaw",
version: "2026.7.1",
})}\n`;
fs.writeFileSync(packageJsonPath, originalPackageJson);
const runCapture = vi.fn();
try {
const cleanup = await prepareBundledAiRuntimePackage(sourceDir, outputDir, runCapture);
expect(runCapture).not.toHaveBeenCalled();
expect(fs.readFileSync(packageJsonPath, "utf8")).toBe(originalPackageJson);
await cleanup();
} finally {
fs.rmSync(sourceDir, { recursive: true, force: true });
fs.rmSync(outputDir, { recursive: true, force: true });
}
});
it("rejects incomplete AI workspace package sources", async () => {
const cases = [
{
dependencies: { "@openclaw/ai": "workspace:*" },
expected: "@openclaw/ai dependency requires the packages/ai workspace",
withWorkspace: false,
},
{
dependencies: {},
expected: "root package.json must declare @openclaw/ai as a dependency",
withWorkspace: true,
},
];
for (const testCase of cases) {
const sourceDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docker-invalid-source-"));
const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docker-invalid-output-"));
fs.writeFileSync(
path.join(sourceDir, "package.json"),
`${JSON.stringify({ dependencies: testCase.dependencies, name: "openclaw" })}\n`,
);
if (testCase.withWorkspace) {
fs.mkdirSync(path.join(sourceDir, "packages", "ai"), { recursive: true });
fs.writeFileSync(path.join(sourceDir, "packages", "ai", "package.json"), "{}\n");
}
try {
await expect(prepareBundledAiRuntimePackage(sourceDir, outputDir, vi.fn())).rejects.toThrow(
testCase.expected,
);
} finally {
fs.rmSync(sourceDir, { recursive: true, force: true });
fs.rmSync(outputDir, { recursive: true, force: true });
}
}
});
it("trims and restores the changelog around ignore-scripts package artifacts", async () => {
const calls: string[] = [];
const tarball = await packOpenClawPackageForDocker("/repo", "/out", {