fix: unblock windows update build

This commit is contained in:
Peter Steinberger
2026-04-08 07:17:07 +01:00
parent 3eb47e9e73
commit 4f5c137f88
11 changed files with 689 additions and 171 deletions

View File

@@ -0,0 +1,72 @@
import { describe, expect, it } from "vitest";
import { BUILD_ALL_STEPS, resolveBuildAllStep } from "../../scripts/build-all.mjs";
describe("resolveBuildAllStep", () => {
it("routes pnpm steps through the npm_execpath pnpm runner on Windows", () => {
const step = BUILD_ALL_STEPS.find((entry) => entry.label === "canvas:a2ui:bundle");
expect(step).toBeTruthy();
const result = resolveBuildAllStep(step, {
platform: "win32",
nodeExecPath: "C:\\Program Files\\nodejs\\node.exe",
npmExecPath: "C:/Users/test/AppData/Local/pnpm/10.32.1/bin/pnpm.cjs",
env: {},
});
expect(result).toEqual({
command: "C:\\Program Files\\nodejs\\node.exe",
args: ["C:/Users/test/AppData/Local/pnpm/10.32.1/bin/pnpm.cjs", "canvas:a2ui:bundle"],
options: {
stdio: "inherit",
env: {},
shell: false,
windowsVerbatimArguments: undefined,
},
});
});
it("keeps node steps on the current node binary", () => {
const step = BUILD_ALL_STEPS.find((entry) => entry.label === "runtime-postbuild");
expect(step).toBeTruthy();
const result = resolveBuildAllStep(step, {
nodeExecPath: "/custom/node",
env: { FOO: "bar" },
});
expect(result).toEqual({
command: "/custom/node",
args: ["scripts/runtime-postbuild.mjs"],
options: {
stdio: "inherit",
env: { FOO: "bar" },
},
});
});
it("adds heap headroom for plugin-sdk dts on Windows", () => {
const step = BUILD_ALL_STEPS.find((entry) => entry.label === "build:plugin-sdk:dts");
expect(step).toBeTruthy();
const result = resolveBuildAllStep(step, {
platform: "win32",
nodeExecPath: "C:\\Program Files\\nodejs\\node.exe",
npmExecPath: "C:/Users/test/AppData/Local/pnpm/10.32.1/bin/pnpm.cjs",
env: { FOO: "bar" },
});
expect(result).toEqual({
command: "C:\\Program Files\\nodejs\\node.exe",
args: ["C:/Users/test/AppData/Local/pnpm/10.32.1/bin/pnpm.cjs", "build:plugin-sdk:dts"],
options: {
stdio: "inherit",
env: {
FOO: "bar",
NODE_OPTIONS: "--max-old-space-size=4096",
},
shell: false,
windowsVerbatimArguments: undefined,
},
});
});
});

View File

@@ -0,0 +1,66 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { stageBundledPluginRuntime } from "../../scripts/stage-bundled-plugin-runtime.mjs";
async function withTempDir(run: (dir: string) => Promise<void>) {
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "openclaw-stage-runtime-"));
try {
await run(dir);
} finally {
await fs.promises.rm(dir, { recursive: true, force: true });
}
}
describe("stageBundledPluginRuntime", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("copies files when Windows rejects runtime overlay symlinks", async () => {
await withTempDir(async (repoRoot) => {
const sourceFile = path.join(
repoRoot,
"dist",
"extensions",
"acpx",
"skills",
"acp-router",
"SKILL.md",
);
await fs.promises.mkdir(path.dirname(sourceFile), { recursive: true });
await fs.promises.writeFile(sourceFile, "skill-body\n", "utf8");
vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const symlinkSpy = vi
.spyOn(fs, "symlinkSync")
.mockImplementation((target, targetPath, type) => {
if (
String(targetPath).includes(`${path.sep}dist-runtime${path.sep}`) &&
type !== "junction"
) {
const error = new Error("no symlink privilege");
Object.assign(error, { code: "EPERM" });
throw error;
}
return undefined;
});
stageBundledPluginRuntime({ repoRoot });
const runtimeFile = path.join(
repoRoot,
"dist-runtime",
"extensions",
"acpx",
"skills",
"acp-router",
"SKILL.md",
);
expect(await fs.promises.readFile(runtimeFile, "utf8")).toBe("skill-body\n");
expect(fs.lstatSync(runtimeFile).isSymbolicLink()).toBe(false);
expect(symlinkSpy).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { resolveTsdownBuildInvocation } from "../../scripts/tsdown-build.mjs";
describe("resolveTsdownBuildInvocation", () => {
it("routes Windows tsdown builds through the pnpm runner instead of shell=true", () => {
const result = resolveTsdownBuildInvocation({
platform: "win32",
nodeExecPath: "C:\\Program Files\\nodejs\\node.exe",
npmExecPath: "C:/Users/test/AppData/Local/pnpm/10.32.1/bin/pnpm.cjs",
env: {},
});
expect(result).toEqual({
command: "C:\\Program Files\\nodejs\\node.exe",
args: [
"C:/Users/test/AppData/Local/pnpm/10.32.1/bin/pnpm.cjs",
"exec",
"tsdown",
"--config-loader",
"unrun",
"--logLevel",
"warn",
],
options: {
encoding: "utf8",
stdio: "pipe",
shell: false,
windowsVerbatimArguments: undefined,
env: {},
},
});
});
});