Files
openclaw/test/scripts/plugin-sdk-api-diff.test.ts
T
Ayaan Zaidi d2afbd05ad refactor(plugin-sdk): replace API baselines with diffs (#123036)
* 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>
2026-08-13 03:45:36 -07:00

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);
});