fix(update): harden root selection

This commit is contained in:
Peter Steinberger
2026-01-10 20:32:15 +01:00
parent 777fb6b7bb
commit 4c4c167416
7 changed files with 209 additions and 46 deletions

30
src/cli/run-main.test.ts Normal file
View File

@@ -0,0 +1,30 @@
import { describe, expect, it } from "vitest";
import { rewriteUpdateFlagArgv } from "./run-main.js";
describe("rewriteUpdateFlagArgv", () => {
it("leaves argv unchanged when --update is absent", () => {
const argv = ["node", "entry.js", "status"];
expect(rewriteUpdateFlagArgv(argv)).toBe(argv);
});
it("rewrites --update into the update command", () => {
expect(rewriteUpdateFlagArgv(["node", "entry.js", "--update"])).toEqual([
"node",
"entry.js",
"update",
]);
});
it("preserves global flags that appear before --update", () => {
expect(
rewriteUpdateFlagArgv(["node", "entry.js", "--profile", "p", "--update"]),
).toEqual(["node", "entry.js", "--profile", "p", "update"]);
});
it("keeps update options after the rewritten command", () => {
expect(
rewriteUpdateFlagArgv(["node", "entry.js", "--update", "--json"]),
).toEqual(["node", "entry.js", "update", "--json"]);
});
});

View File

@@ -8,7 +8,15 @@ import { ensureClawdbotCliOnPath } from "../infra/path-env.js";
import { assertSupportedRuntime } from "../infra/runtime-guard.js";
import { installUnhandledRejectionHandler } from "../infra/unhandled-rejections.js";
import { enableConsoleCapture } from "../logging.js";
import { updateCommand } from "./update-cli.js";
export function rewriteUpdateFlagArgv(argv: string[]): string[] {
const index = argv.indexOf("--update");
if (index === -1) return argv;
const next = [...argv];
next.splice(index, 1, "update");
return next;
}
export async function runCli(argv: string[] = process.argv) {
loadDotEnv({ quiet: true });
@@ -21,12 +29,6 @@ export async function runCli(argv: string[] = process.argv) {
// Enforce the minimum supported runtime before doing any work.
assertSupportedRuntime();
// Handle --update flag before full program parsing
if (argv.includes("--update")) {
await updateCommand({});
return;
}
const { buildProgram } = await import("./program.js");
const program = buildProgram();
@@ -42,7 +44,7 @@ export async function runCli(argv: string[] = process.argv) {
process.exit(1);
});
await program.parseAsync(argv);
await program.parseAsync(rewriteUpdateFlagArgv(argv));
}
export function isCliMainModule(): boolean {

View File

@@ -1,5 +1,6 @@
import type { Command } from "commander";
import { resolveClawdbotPackageRoot } from "../infra/clawdbot-root.js";
import {
runGatewayUpdate,
type UpdateRunResult,
@@ -103,8 +104,15 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
defaultRuntime.log("");
}
const root =
(await resolveClawdbotPackageRoot({
moduleUrl: import.meta.url,
argv1: process.argv[1],
cwd: process.cwd(),
})) ?? process.cwd();
const result = await runGatewayUpdate({
cwd: process.cwd(),
cwd: root,
argv1: process.argv[1],
timeoutMs,
});
@@ -124,6 +132,18 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
),
);
}
if (result.reason === "not-git-install") {
defaultRuntime.log(
theme.warn(
"Skipped: this Clawdbot install isn't a git checkout. Update via your package manager, then run `clawdbot doctor` and `clawdbot daemon restart`.",
),
);
defaultRuntime.log(
theme.muted(
"Examples: `npm i -g clawdbot@latest` or `pnpm add -g clawdbot@latest`",
),
);
}
defaultRuntime.exit(0);
return;
}
@@ -141,9 +161,7 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
}
} catch (err) {
if (!opts.json) {
defaultRuntime.log(
theme.warn(`Daemon restart failed: ${String(err)}`),
);
defaultRuntime.log(theme.warn(`Daemon restart failed: ${String(err)}`));
defaultRuntime.log(
theme.muted(
"You may need to restart the daemon manually: clawdbot daemon restart",
@@ -179,14 +197,14 @@ export function registerUpdateCli(program: Command) {
"after",
`
Examples:
clawdbot update # Update from git or package manager
clawdbot update # Update a source checkout (git)
clawdbot update --restart # Update and restart the daemon
clawdbot update --json # Output result as JSON
clawdbot --update # Shorthand for clawdbot update
Notes:
- For git installs: fetches, rebases, installs deps, builds, and runs doctor
- For npm installs: runs package manager update command
- For npm installs: use npm/pnpm to reinstall (see docs/install/updating.md)
- Skips update if the working directory has uncommitted changes
`,
)