fix(ci): restore macOS and Windows QA gates

Restores Azure native Windows hydrated node_modules bootstrap, fixes the macOS settings SwiftFormat drift, and stabilizes lifecycle process-group CI proof.

Proof:
- `tbx_01kvrpr5kfc58wdnakx2zkc4k6`: `corepack pnpm test:serial test/scripts/plugin-lifecycle-measure.test.ts` passed.
- `tbx_01kvrpvcrmsxgyb886pa127qq3`: `OPENCLAW_TESTBOX=1 ... corepack pnpm check:changed` passed.
- `tbx_01kvrpzpafmp27tyb4tg9yvwvz`: touched-file `format:check` passed.
- PR CI `27988226071` passed, including `macos-node`, `macos-swift`, and `checks-node-compact-small-whole-2`.
This commit is contained in:
Vincent Koc 2026-06-23 06:38:27 +08:00 committed by GitHub
parent 961130c707
commit dcb6b0dd6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 74 additions and 46 deletions

View file

@ -680,7 +680,6 @@ struct GeneralSettings: View {
case .missingNode, .missingGateway, .incompatible, .error: .orange
}
}
}
private enum RemoteStatus: Equatable {

View file

@ -1987,6 +1987,10 @@ function isAwsMacosRemoteTarget(commandArgs, providerName) {
);
}
function isHydratedNativeWindowsProvider(providerName) {
return providerName === "aws" || providerName === "azure";
}
function remoteWindowsHydratedNodeModulesBootstrap() {
return [
"$openclawModulesDir = $env:PNPM_CONFIG_MODULES_DIR",
@ -2004,7 +2008,7 @@ function injectRemoteWindowsHydratedNodeModulesBootstrap(commandArgs, providerNa
const runtimeEntrypoint = commandRuntimeEntrypoint(runCommandArgs(commandArgs));
if (
commandArgs[0] !== "run" ||
providerName !== "aws" ||
!isHydratedNativeWindowsProvider(providerName) ||
!isNativeWindowsRemoteTarget(commandArgs) ||
!hasOption(commandArgs, "--id") ||
!runtimeEntrypoint

View file

@ -2407,6 +2407,32 @@ describe.concurrent("scripts/crabbox-wrapper", () => {
expect(remoteCommand).toContain("corepack pnpm check:changed");
});
it("restores hydrated node_modules before Azure native Windows shell commands", () => {
const result = runWrapper("provider: hetzner, aws, azure, local-container\n", [
"run",
"--provider",
"azure",
"--target",
"windows",
"--windows-mode",
"normal",
"--id",
"cbx_test",
"--shell",
"--",
"corepack pnpm check:changed",
]);
const output = parseFakeCrabboxOutput(result);
const remoteCommand = output.args.at(-1) ?? "";
expect(result.status).toBe(0);
expect(output.args).toContain("--shell");
expect(remoteCommand).toContain("$openclawModulesDir = $env:PNPM_CONFIG_MODULES_DIR");
expect(remoteCommand).toContain('mklink /J "$openclawSelfModules" "$openclawModulesDir"');
expect(remoteCommand).toContain('mklink /J "$openclawWorkspaceModules" "$openclawModulesDir"');
expect(remoteCommand).toContain("corepack pnpm check:changed");
});
it("restores hydrated node_modules before AWS native Windows direct commands", () => {
const result = runWrapper("provider: hetzner, aws, azure, local-container\n", [
"run",

View file

@ -15,9 +15,6 @@ import { afterEach, describe, expect, it } from "vitest";
const tempDirs: string[] = [];
const scriptPath = "scripts/e2e/lib/plugin-lifecycle-matrix/measure.mjs";
const hasTimeoutCommand =
process.platform === "linux" &&
spawnSync("bash", ["-lc", "command -v timeout >/dev/null 2>&1"]).status === 0;
function makeTempDir(): string {
const dir = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-lifecycle-measure-"));
@ -317,8 +314,8 @@ describe("plugin lifecycle resource sampler", () => {
encoding: "utf8",
env: {
...process.env,
OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS: "150",
OPENCLAW_PLUGIN_LIFECYCLE_TIMEOUT_KILL_GRACE_MS: "100",
OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS: "1000",
OPENCLAW_PLUGIN_LIFECYCLE_TIMEOUT_KILL_GRACE_MS: "200",
PID_FILE: pidFile,
},
timeout: 5000,
@ -340,49 +337,51 @@ describe("plugin lifecycle resource sampler", () => {
},
);
it.runIf(hasTimeoutCommand)("forwards external termination to the measured process group", () => {
const dir = makeTempDir();
const summary = path.join(dir, "summary.tsv");
const pidFile = path.join(dir, "descendant.pid");
let descendantPid;
it.runIf(process.platform === "linux")(
"forwards external termination to the measured process group",
async () => {
const dir = makeTempDir();
const summary = path.join(dir, "summary.tsv");
const pidFile = path.join(dir, "descendant.pid");
let descendantPid;
try {
const result = spawnSync(
"timeout",
[
"--kill-after=1s",
"0.2s",
"node",
scriptPath,
summary,
"external-stop",
"--",
"bash",
"-lc",
'bash -c \'trap "" TERM; printf "%s\\n" "$$" >"$PID_FILE"; while :; do sleep 1; done\' & wait',
],
{
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS: "5000",
OPENCLAW_PLUGIN_LIFECYCLE_TIMEOUT_KILL_GRACE_MS: "100",
PID_FILE: pidFile,
try {
const result = spawn(
process.execPath,
[
scriptPath,
summary,
"external-stop",
"--",
"bash",
"-lc",
'bash -c \'trap "" TERM; printf "%s\\n" "$$" >"$PID_FILE"; while :; do sleep 1; done\' & wait',
],
{
cwd: process.cwd(),
env: {
...process.env,
OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS: "5000",
OPENCLAW_PLUGIN_LIFECYCLE_TIMEOUT_KILL_GRACE_MS: "200",
PID_FILE: pidFile,
},
stdio: "ignore",
},
timeout: 5000,
},
);
);
descendantPid = Number.parseInt(readFileSync(pidFile, "utf8"), 10);
expect(result.status).toBe(124);
expect(waitForPidExit(descendantPid, 1000)).toBe(true);
} finally {
if (descendantPid > 0 && pidExists(descendantPid)) {
process.kill(descendantPid, "SIGKILL");
expect(waitForPath(pidFile, 2000)).toBe(true);
descendantPid = Number.parseInt(readFileSync(pidFile, "utf8"), 10);
result.kill("SIGTERM");
const close = await waitForChildClose(result, 5000);
expect(close.signal).toBe("SIGTERM");
expect(waitForPidExit(descendantPid, 1000)).toBe(true);
} finally {
if (descendantPid > 0 && pidExists(descendantPid)) {
process.kill(descendantPid, "SIGKILL");
}
}
}
});
},
);
it.runIf(process.platform === "linux")(
"exits promptly when externally terminated phases stop during grace",