mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-14 06:33:09 -06:00
d2afbd05ad
* refactor(plugin-sdk): replace API baselines with diffs * perf(plugin-sdk): bound API diff resources * fix(plugin-sdk): isolate API diff dependencies * fix(release): forward Plugin SDK acknowledgement * fix(release): enforce SDK acknowledgement on publish * chore: preserve generated-doc ignore policy * fix(release): freeze SDK API evidence before publish * fix(ci): satisfy SDK evidence guards * fix(release): bind complete SDK evidence * fix(release): authenticate plugin SDK evidence * fix(plugin-sdk): abort interrupted API diffs * test(ui): freeze page clock in background-tasks rail e2e The rail transcript is compared byte-for-byte across the detail-panel round-trip while it renders live relative ages; on slow CI runners the second boundary ticks between the two reads (11s -> 12s) and fails the equality assertion. Fix the page Date with Playwright setFixedTime while keeping timers running so the tasks.list polling assertions still hold. Repro: a 1.5s stall between the reads fails pre-fix with the exact CI diff and passes post-fix. * fix(scripts): drop unused export on dependency-evidence CLI main Knip's workflow scan re-roots script references after an actions/checkout step that sets path:, so the new trusted-tooling checkout in openclaw-npm-release.yml stops marking this CLI as a workflow entry and its exported main() surfaces as an unused export in check-dependencies. Nothing imports main; the module invokes it through its own entry guard, so the export keyword was dead surface either way. --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
import { execFileSync, spawn } from "node:child_process";
|
|
import { chmodSync, existsSync, readdirSync, writeFileSync } from "node:fs";
|
|
import { delimiter, join, resolve } from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
|
|
|
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
|
|
|
function git(repo: string, args: string[]): string {
|
|
return execFileSync("git", args, { cwd: repo, encoding: "utf8" });
|
|
}
|
|
|
|
async function waitFor(check: () => boolean, timeoutMs: number): Promise<void> {
|
|
const deadline = Date.now() + timeoutMs;
|
|
while (!check()) {
|
|
if (Date.now() >= deadline) {
|
|
throw new Error("timed out waiting for Plugin SDK API diff child");
|
|
}
|
|
await new Promise((resolveWait) => {
|
|
setTimeout(resolveWait, 25);
|
|
});
|
|
}
|
|
}
|
|
|
|
describe("Plugin SDK API diff CLI", () => {
|
|
it("interrupts a running child and removes its registered worktree", async () => {
|
|
const repo = git(process.cwd(), ["rev-parse", "--show-toplevel"]).trim();
|
|
const runnerTemp = tempDirs.make("plugin-sdk-api-diff-temp-");
|
|
const binDir = tempDirs.make("plugin-sdk-api-diff-bin-");
|
|
const pnpmMarker = join(binDir, "pnpm-started");
|
|
|
|
const fakePnpm = join(binDir, "pnpm");
|
|
writeFileSync(
|
|
fakePnpm,
|
|
"#!/bin/sh\n: > \"$PNPM_MARKER\"\ntrap 'exit 143' INT TERM\nwhile :; do sleep 1; done\n",
|
|
);
|
|
chmodSync(fakePnpm, 0o755);
|
|
|
|
const child = spawn(
|
|
process.execPath,
|
|
[
|
|
"--import",
|
|
import.meta.resolve("tsx"),
|
|
resolve("scripts/plugin-sdk-api-diff.mts"),
|
|
"--base",
|
|
"HEAD",
|
|
"--head",
|
|
"HEAD",
|
|
],
|
|
{
|
|
cwd: repo,
|
|
env: {
|
|
...process.env,
|
|
PATH: `${binDir}${delimiter}${process.env.PATH ?? ""}`,
|
|
PNPM_MARKER: pnpmMarker,
|
|
RUNNER_TEMP: runnerTemp,
|
|
},
|
|
stdio: ["ignore", "ignore", "pipe"],
|
|
},
|
|
);
|
|
|
|
let closed = false;
|
|
let stderr = "";
|
|
child.stderr.setEncoding("utf8");
|
|
child.stderr.on("data", (chunk: string) => {
|
|
stderr += chunk;
|
|
});
|
|
const close = new Promise<number | null>((resolveClose) => {
|
|
child.once("close", (code) => {
|
|
closed = true;
|
|
resolveClose(code);
|
|
});
|
|
});
|
|
try {
|
|
await waitFor(() => existsSync(pnpmMarker) || closed, 10_000);
|
|
expect(closed, stderr).toBe(false);
|
|
expect(git(repo, ["worktree", "list"])).toContain(runnerTemp);
|
|
const interruptedAt = Date.now();
|
|
child.kill("SIGTERM");
|
|
const exitCode = await Promise.race([
|
|
close,
|
|
new Promise<never>((_, reject) => {
|
|
setTimeout(() => reject(new Error("Plugin SDK API diff ignored SIGTERM")), 5_000);
|
|
}),
|
|
]);
|
|
|
|
expect(exitCode).toBe(143);
|
|
expect(Date.now() - interruptedAt).toBeLessThan(5_000);
|
|
expect(git(repo, ["worktree", "list"])).not.toContain(runnerTemp);
|
|
expect(readdirSync(runnerTemp)).toEqual([]);
|
|
} finally {
|
|
if (!closed) {
|
|
child.kill("SIGKILL");
|
|
await close;
|
|
}
|
|
}
|
|
}, 15_000);
|
|
});
|