mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
fix(update): avoid lint-blocked dev installs (#77181)
This commit is contained in:
@@ -109,6 +109,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/channels: skip config, proxy, channel-option catalog, banner-config, and plugin startup bootstrap for the bare `openclaw channels` parent-help command, so it exits promptly after printing help instead of loading configured channel plugins. Thanks @vincentkoc.
|
||||
- CLI/gateway: pause non-TTY stdin after full CLI command completion and stop `openclaw agent` from falling back to embedded mode after gateway request/auth failures, so parent help commands exit cleanly and scoped delivery probes surface the real Gateway error immediately. Thanks @vincentkoc.
|
||||
- Gateway/model catalog: cache empty read-only model catalog results until reload, so TUI and control-plane refresh loops cannot hammer plugin metadata reads when no usable models are currently discovered. Thanks @vincentkoc.
|
||||
- CLI/update: make dev-channel preflight lint opt-in and constrained when enabled, so `openclaw update --channel dev` no longer walks back otherwise-good main commits when Ubuntu hosts OOM-kill or fail parallel oxlint shards. Thanks @vincentkoc.
|
||||
- Google Meet: fork the caller's current agent transcript into agent-mode meeting consultant sessions, so Meet replies inherit the context from the tool call that joined the meeting.
|
||||
- Google Meet: log the concrete agent-mode TTS provider, model, voice, output format, and sample rate after speech synthesis, so Meet logs show which voice backend spoke each reply.
|
||||
- Google Meet: log the resolved audio provider model when starting Chrome and paired-node Meet talk-back bridges, so agent-mode joins show the STT model and bidi joins show the realtime voice model.
|
||||
|
||||
@@ -148,7 +148,7 @@ manually.
|
||||
Dev only.
|
||||
</Step>
|
||||
<Step title="Preflight build (dev only)">
|
||||
Runs lint and TypeScript build in a temp worktree. If the tip fails, walks back up to 10 commits to find the newest clean build.
|
||||
Runs the TypeScript build in a temp worktree. If the tip fails, walks back up to 10 commits to find the newest buildable commit. Set `OPENCLAW_UPDATE_PREFLIGHT_LINT=1` to also run lint during this preflight; lint runs in constrained serial mode because user update hosts are often smaller than CI runners.
|
||||
</Step>
|
||||
<Step title="Rebase">
|
||||
Rebases onto the selected commit (dev only).
|
||||
|
||||
@@ -35,9 +35,20 @@ const shards = [
|
||||
},
|
||||
];
|
||||
|
||||
const results = await Promise.all(shards.map((shard) => runShard(shard)));
|
||||
const runSerial = process.env.OPENCLAW_OXLINT_SHARDS_SERIAL === "1";
|
||||
const results = runSerial
|
||||
? await runShardsSerial(shards)
|
||||
: await Promise.all(shards.map((shard) => runShard(shard)));
|
||||
process.exitCode = results.find((status) => status !== 0) ?? 0;
|
||||
|
||||
async function runShardsSerial(entries) {
|
||||
const results = [];
|
||||
for (const shard of entries) {
|
||||
results.push(await runShard(shard));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async function runShard(shard) {
|
||||
console.error(`[oxlint:${shard.name}] starting`);
|
||||
const child = spawn(process.execPath, [runner, ...shard.args, ...extraArgs], {
|
||||
|
||||
@@ -614,11 +614,107 @@ describe("runGatewayUpdate", () => {
|
||||
expect(calls.some((call) => call.startsWith("npm install --prefix "))).toBe(true);
|
||||
expect(calls).toContain("pnpm install");
|
||||
expect(calls).toContain("pnpm build");
|
||||
expect(calls).toContain("pnpm lint");
|
||||
expect(calls).not.toContain("pnpm lint");
|
||||
expect(calls).toContain("pnpm ui:build");
|
||||
expect(pnpmEnvPaths.some((value) => value.includes("openclaw-update-pnpm-"))).toBe(true);
|
||||
});
|
||||
|
||||
it("runs dev preflight lint in constrained mode when explicitly enabled", async () => {
|
||||
await setupGitPackageManagerFixture();
|
||||
const calls: string[] = [];
|
||||
const lintEnv: NodeJS.ProcessEnv[] = [];
|
||||
const upstreamSha = "upstream123";
|
||||
const doctorNodePath = await resolveStableNodePath(process.execPath);
|
||||
const doctorCommand = `${doctorNodePath} ${path.join(tempDir, "openclaw.mjs")} doctor --non-interactive --fix`;
|
||||
|
||||
const runCommand = async (
|
||||
argv: string[],
|
||||
options?: { env?: NodeJS.ProcessEnv; cwd?: string; timeoutMs?: number },
|
||||
) => {
|
||||
const key = argv.join(" ");
|
||||
calls.push(key);
|
||||
|
||||
if (key === `git -C ${tempDir} rev-parse --show-toplevel`) {
|
||||
return { stdout: tempDir, stderr: "", code: 0 };
|
||||
}
|
||||
if (key === `git -C ${tempDir} rev-parse HEAD`) {
|
||||
return { stdout: "abc123", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === `git -C ${tempDir} rev-parse --abbrev-ref HEAD`) {
|
||||
return { stdout: "main", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === `git -C ${tempDir} status --porcelain -- :!dist/control-ui/`) {
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === `git -C ${tempDir} rev-parse --abbrev-ref --symbolic-full-name @{upstream}`) {
|
||||
return { stdout: "origin/main", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === `git -C ${tempDir} fetch --all --prune --tags`) {
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === `git -C ${tempDir} rev-parse @{upstream}`) {
|
||||
return { stdout: upstreamSha, stderr: "", code: 0 };
|
||||
}
|
||||
if (key === `git -C ${tempDir} rev-list --max-count=10 ${upstreamSha}`) {
|
||||
return { stdout: `${upstreamSha}\n`, stderr: "", code: 0 };
|
||||
}
|
||||
if (key === "pnpm --version") {
|
||||
return { stdout: "10.0.0", stderr: "", code: 0 };
|
||||
}
|
||||
if (
|
||||
key.startsWith(`git -C ${tempDir} worktree add --detach /tmp/`) &&
|
||||
key.endsWith(` ${upstreamSha}`) &&
|
||||
preflightPrefixPattern.test(key)
|
||||
) {
|
||||
return { stdout: `HEAD is now at ${upstreamSha}`, stderr: "", code: 0 };
|
||||
}
|
||||
if (
|
||||
key.startsWith("git -C /tmp/") &&
|
||||
preflightPrefixPattern.test(key) &&
|
||||
key.includes(" checkout --detach ") &&
|
||||
key.endsWith(upstreamSha)
|
||||
) {
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === "pnpm install" || key === "pnpm build" || key === "pnpm ui:build") {
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === "pnpm lint") {
|
||||
lintEnv.push(options?.env ?? {});
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
if (
|
||||
key.startsWith(`git -C ${tempDir} worktree remove --force /tmp/`) &&
|
||||
preflightPrefixPattern.test(key)
|
||||
) {
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === `git -C ${tempDir} worktree prune`) {
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === `git -C ${tempDir} rebase ${upstreamSha}`) {
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
if (key === doctorCommand) {
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
};
|
||||
|
||||
const result = await withEnvAsync({ OPENCLAW_UPDATE_PREFLIGHT_LINT: "1" }, async () =>
|
||||
runWithCommand(runCommand, { channel: "dev" }),
|
||||
);
|
||||
|
||||
expect(result.status).toBe("ok");
|
||||
expect(calls).toContain("pnpm lint");
|
||||
expect(lintEnv).toHaveLength(1);
|
||||
expect(lintEnv[0]).toMatchObject({
|
||||
OPENCLAW_LOCAL_CHECK: "1",
|
||||
OPENCLAW_LOCAL_CHECK_MODE: "throttled",
|
||||
OPENCLAW_OXLINT_SHARDS_SERIAL: "1",
|
||||
});
|
||||
});
|
||||
|
||||
it("retries windows pnpm git installs with --ignore-scripts for dev updates", async () => {
|
||||
await setupGitPackageManagerFixture();
|
||||
const calls: string[] = [];
|
||||
|
||||
@@ -176,6 +176,12 @@ const PREFLIGHT_WORKTREE_DIRNAME = process.platform === "win32" ? "wt" : "worktr
|
||||
const PREFLIGHT_CLEANUP_TIMEOUT_MS = 60_000;
|
||||
const WINDOWS_PREFLIGHT_BASE_DIR = "ocu";
|
||||
const WINDOWS_BUILD_MAX_OLD_SPACE_MB = 4096;
|
||||
const DEV_PREFLIGHT_LINT_ENV: NodeJS.ProcessEnv = {
|
||||
OPENCLAW_LOCAL_CHECK: "1",
|
||||
OPENCLAW_LOCAL_CHECK_MODE: "throttled",
|
||||
OPENCLAW_OXLINT_SHARDS_SERIAL: "1",
|
||||
};
|
||||
const DEV_PREFLIGHT_LINT_OPT_IN_ENV = "OPENCLAW_UPDATE_PREFLIGHT_LINT";
|
||||
|
||||
function normalizeDir(value?: string | null) {
|
||||
if (!value) {
|
||||
@@ -566,8 +572,16 @@ function mergeCommandEnvironments(
|
||||
};
|
||||
}
|
||||
|
||||
function shouldRunDevPreflightLint(): boolean {
|
||||
return process.platform !== "win32";
|
||||
function shouldRunDevPreflightLint(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
const value = env[DEV_PREFLIGHT_LINT_OPT_IN_ENV]?.trim().toLowerCase();
|
||||
return value === "1" || value === "true";
|
||||
}
|
||||
|
||||
function resolveDevPreflightLintEnv(env: NodeJS.ProcessEnv | undefined): NodeJS.ProcessEnv {
|
||||
return {
|
||||
...env,
|
||||
...DEV_PREFLIGHT_LINT_ENV,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeFallbackFailureReason(stepName: string): NonNullable<UpdateRunResult["reason"]> {
|
||||
@@ -1042,7 +1056,7 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
|
||||
`preflight lint (${shortSha})`,
|
||||
managerScriptArgs(manager.manager, "lint"),
|
||||
worktreeDir,
|
||||
manager.env,
|
||||
resolveDevPreflightLintEnv(manager.env),
|
||||
),
|
||||
);
|
||||
steps.push(lintStep);
|
||||
|
||||
@@ -34,6 +34,13 @@ describe("run-oxlint", () => {
|
||||
expect(shardedLintRunner).toContain('OPENCLAW_OXLINT_SKIP_PREPARE: "1"');
|
||||
});
|
||||
|
||||
it("lets dev update preflight run oxlint shards serially", () => {
|
||||
const shardedLintRunner = readFileSync("scripts/run-oxlint-shards.mjs", "utf8");
|
||||
|
||||
expect(shardedLintRunner).toContain("OPENCLAW_OXLINT_SHARDS_SERIAL");
|
||||
expect(shardedLintRunner).toContain("runShardsSerial");
|
||||
});
|
||||
|
||||
it("filters tracked targets missing from sparse checkouts", () => {
|
||||
const result = filterSparseMissingOxlintTargets(
|
||||
["--tsconfig", "config/tsconfig/oxlint.core.json", "src", "ui", "packages", "--threads=1"],
|
||||
|
||||
Reference in New Issue
Block a user