fix(update): reject openclaw source package targets

This commit is contained in:
Vincent Koc
2026-05-22 07:20:49 +08:00
parent fad1c8a071
commit 15a0156a8c
19 changed files with 254 additions and 36 deletions
+16 -16
View File
@@ -2074,22 +2074,6 @@ describe("update-cli", () => {
},
expectedSpec: "openclaw@next",
},
{
name: "main shorthand",
run: async () => {
mockPackageInstallStatus(createCaseDir("openclaw-update"));
await updateCommand({ yes: true, tag: "main" });
},
expectedSpec: "github:openclaw/openclaw#main",
},
{
name: "explicit git package spec",
run: async () => {
mockPackageInstallStatus(createCaseDir("openclaw-update"));
await updateCommand({ yes: true, tag: "github:openclaw/openclaw#main" });
},
expectedSpec: "github:openclaw/openclaw#main",
},
{
name: "OPENCLAW_UPDATE_PACKAGE_SPEC override",
run: async () => {
@@ -2116,6 +2100,22 @@ describe("update-cli", () => {
},
);
it.each(["main", "github:openclaw/openclaw#main", "openclaw@github:openclaw/openclaw#main"])(
"rejects OpenClaw GitHub source package updates: %s",
async (tag) => {
mockPackageInstallStatus(createCaseDir("openclaw-update"));
await updateCommand({ yes: true, tag });
expect(packageInstallCommandCall()).toBeUndefined();
expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
const errors = vi.mocked(defaultRuntime.error).mock.calls.map((call) => String(call[0]));
expect(errors.join("\n")).toContain("Unsupported package update target");
expect(errors.join("\n")).toContain("openclaw/openclaw");
expect(errors.join("\n")).toContain("openclaw update --channel dev");
},
);
it("fails package updates when the installed correction version does not match the requested target", async () => {
const tempDir = createCaseDir("openclaw-update");
const nodeModules = path.join(tempDir, "node_modules");
+1 -1
View File
@@ -58,7 +58,6 @@ export function registerUpdateCli(program: Command) {
["openclaw update --channel beta", "Switch to beta channel (git + npm)"],
["openclaw update --channel dev", "Switch to dev channel (git + npm)"],
["openclaw update --tag beta", "One-off update to a dist-tag or version"],
["openclaw update --tag main", "One-off package install from GitHub main"],
["openclaw update --dry-run", "Preview actions without changing anything"],
["openclaw update --no-restart", "Update without restarting the service"],
["openclaw update --json", "Output result as JSON"],
@@ -78,6 +77,7 @@ ${theme.heading("Switch channels:")}
- Use --channel stable|beta|dev to persist the update channel in config
- Run openclaw update status to see the active channel and source
- Use --tag <dist-tag|version|spec> for a one-off package update without persisting
- Use --channel dev, not --tag main, for the moving GitHub main checkout
${theme.heading("Non-interactive:")}
- Use --yes to accept downgrade prompts
+14
View File
@@ -69,6 +69,7 @@ import {
createGlobalInstallEnv,
cleanupGlobalRenameDirs,
globalInstallArgs,
isOpenClawSourcePackageInstallSpec,
resolveGlobalInstallTarget,
resolveGlobalInstallSpec,
resolvePnpmGlobalDirFromGlobalRoot,
@@ -976,6 +977,14 @@ async function resolvePackageRuntimePreflightError(params: {
].join("\n");
}
function formatUnsupportedOpenClawSourcePackageTargetMessage(target: string): string {
return [
`Unsupported package update target: ${target}.`,
"OpenClaw package updates use published npm artifacts or built tarballs; npm GitHub source installs for openclaw/openclaw do not reliably produce an installable package.",
"Use `openclaw update --channel dev` for the moving main checkout, or target `latest`, `beta`, an exact version, or a built `.tgz` package spec.",
].join("\n");
}
async function resolvePackageRuntimeForPreflight(params: {
nodeRunner?: string;
timeoutMs?: number;
@@ -3080,6 +3089,11 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
tag,
env: process.env,
});
if (isOpenClawSourcePackageInstallSpec(packageInstallSpec)) {
defaultRuntime.error(formatUnsupportedOpenClawSourcePackageTargetMessage(packageInstallSpec));
defaultRuntime.exit(1);
return;
}
}
if (opts.dryRun) {