mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix(update): repair daemon-cli compat exports after self-update
This commit is contained in:
@@ -101,6 +101,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Discord: allow channel-edit to archive/lock threads and set auto-archive duration. (#5542) Thanks @stumct.
|
||||
- Discord tests: use a partial @buape/carbon mock in slash command coverage. (#13262) Thanks @arosstale.
|
||||
- Tests: update thread ID handling in Slack message collection tests. (#14108) Thanks @swizzmagik.
|
||||
- Update/Daemon: fix post-update restart compatibility by generating `dist/cli/daemon-cli.js` with alias-aware exports from hashed daemon bundles, preventing `registerDaemonCli` import failures during `openclaw update`.
|
||||
|
||||
## 2026.2.9
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
LEGACY_DAEMON_CLI_EXPORTS,
|
||||
resolveLegacyDaemonCliAccessors,
|
||||
} from "../src/cli/daemon-cli-compat.ts";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const distDir = path.join(rootDir, "dist");
|
||||
@@ -27,12 +31,32 @@ if (candidates.length === 0) {
|
||||
throw new Error("No daemon-cli bundle found in dist; cannot write legacy CLI shim.");
|
||||
}
|
||||
|
||||
const target = candidates.toSorted()[0];
|
||||
const orderedCandidates = candidates.toSorted();
|
||||
const resolved = orderedCandidates
|
||||
.map((entry) => {
|
||||
const source = fs.readFileSync(path.join(distDir, entry), "utf8");
|
||||
const accessors = resolveLegacyDaemonCliAccessors(source);
|
||||
return { entry, accessors };
|
||||
})
|
||||
.find((entry) => Boolean(entry.accessors));
|
||||
|
||||
if (!resolved?.accessors) {
|
||||
throw new Error(
|
||||
`Could not resolve daemon-cli export aliases from dist bundles: ${orderedCandidates.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const target = resolved.entry;
|
||||
const relPath = `../${target}`;
|
||||
const { accessors } = resolved;
|
||||
|
||||
const contents =
|
||||
"// Legacy shim for pre-tsdown update-cli imports.\n" +
|
||||
`export { registerDaemonCli, runDaemonInstall, runDaemonRestart, runDaemonStart, runDaemonStatus, runDaemonStop, runDaemonUninstall } from "${relPath}";\n`;
|
||||
`import * as daemonCli from "${relPath}";\n` +
|
||||
LEGACY_DAEMON_CLI_EXPORTS.map(
|
||||
(name) => `export const ${name} = daemonCli.${accessors[name]};`,
|
||||
).join("\n") +
|
||||
"\n";
|
||||
|
||||
fs.mkdirSync(cliDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(cliDir, "daemon-cli.js"), contents);
|
||||
|
||||
30
src/cli/daemon-cli-compat.test.ts
Normal file
30
src/cli/daemon-cli-compat.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveLegacyDaemonCliAccessors } from "./daemon-cli-compat.js";
|
||||
|
||||
describe("resolveLegacyDaemonCliAccessors", () => {
|
||||
it("resolves aliased daemon-cli exports from a bundled chunk", () => {
|
||||
const bundle = `
|
||||
var daemon_cli_exports = /* @__PURE__ */ __exportAll({ registerDaemonCli: () => registerDaemonCli });
|
||||
export { runDaemonStop as a, runDaemonStart as i, runDaemonStatus as n, runDaemonUninstall as o, runDaemonRestart as r, runDaemonInstall as s, daemon_cli_exports as t };
|
||||
`;
|
||||
|
||||
expect(resolveLegacyDaemonCliAccessors(bundle)).toEqual({
|
||||
registerDaemonCli: "t.registerDaemonCli",
|
||||
runDaemonInstall: "s",
|
||||
runDaemonRestart: "r",
|
||||
runDaemonStart: "i",
|
||||
runDaemonStatus: "n",
|
||||
runDaemonStop: "a",
|
||||
runDaemonUninstall: "o",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null when required aliases are missing", () => {
|
||||
const bundle = `
|
||||
var daemon_cli_exports = /* @__PURE__ */ __exportAll({ registerDaemonCli: () => registerDaemonCli });
|
||||
export { runDaemonRestart as r, daemon_cli_exports as t };
|
||||
`;
|
||||
|
||||
expect(resolveLegacyDaemonCliAccessors(bundle)).toBeNull();
|
||||
});
|
||||
});
|
||||
92
src/cli/daemon-cli-compat.ts
Normal file
92
src/cli/daemon-cli-compat.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
export const LEGACY_DAEMON_CLI_EXPORTS = [
|
||||
"registerDaemonCli",
|
||||
"runDaemonInstall",
|
||||
"runDaemonRestart",
|
||||
"runDaemonStart",
|
||||
"runDaemonStatus",
|
||||
"runDaemonStop",
|
||||
"runDaemonUninstall",
|
||||
] as const;
|
||||
|
||||
type LegacyDaemonCliExport = (typeof LEGACY_DAEMON_CLI_EXPORTS)[number];
|
||||
|
||||
const EXPORT_SPEC_RE = /^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/;
|
||||
const REGISTER_CONTAINER_RE =
|
||||
/(?:var|const|let)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:\/\*[\s\S]*?\*\/\s*)?__exportAll\(\{\s*registerDaemonCli\s*:\s*\(\)\s*=>\s*registerDaemonCli\s*\}\)/;
|
||||
|
||||
function parseExportAliases(bundleSource: string): Map<string, string> | null {
|
||||
const matches = [...bundleSource.matchAll(/export\s*\{([^}]+)\}\s*;?/g)];
|
||||
if (matches.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const last = matches.at(-1);
|
||||
const body = last?.[1];
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const aliases = new Map<string, string>();
|
||||
for (const chunk of body.split(",")) {
|
||||
const spec = chunk.trim();
|
||||
if (!spec) {
|
||||
continue;
|
||||
}
|
||||
const parsed = spec.match(EXPORT_SPEC_RE);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
const original = parsed[1];
|
||||
const alias = parsed[2] ?? original;
|
||||
aliases.set(original, alias);
|
||||
}
|
||||
return aliases;
|
||||
}
|
||||
|
||||
function findRegisterContainerSymbol(bundleSource: string): string | null {
|
||||
return bundleSource.match(REGISTER_CONTAINER_RE)?.[1] ?? null;
|
||||
}
|
||||
|
||||
export function resolveLegacyDaemonCliAccessors(
|
||||
bundleSource: string,
|
||||
): Record<LegacyDaemonCliExport, string> | null {
|
||||
const aliases = parseExportAliases(bundleSource);
|
||||
if (!aliases) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const registerContainer = findRegisterContainerSymbol(bundleSource);
|
||||
if (!registerContainer) {
|
||||
return null;
|
||||
}
|
||||
const registerContainerAlias = aliases.get(registerContainer);
|
||||
if (!registerContainerAlias) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const runDaemonInstall = aliases.get("runDaemonInstall");
|
||||
const runDaemonRestart = aliases.get("runDaemonRestart");
|
||||
const runDaemonStart = aliases.get("runDaemonStart");
|
||||
const runDaemonStatus = aliases.get("runDaemonStatus");
|
||||
const runDaemonStop = aliases.get("runDaemonStop");
|
||||
const runDaemonUninstall = aliases.get("runDaemonUninstall");
|
||||
if (
|
||||
!runDaemonInstall ||
|
||||
!runDaemonRestart ||
|
||||
!runDaemonStart ||
|
||||
!runDaemonStatus ||
|
||||
!runDaemonStop ||
|
||||
!runDaemonUninstall
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
registerDaemonCli: `${registerContainerAlias}.registerDaemonCli`,
|
||||
runDaemonInstall,
|
||||
runDaemonRestart,
|
||||
runDaemonStart,
|
||||
runDaemonStatus,
|
||||
runDaemonStop,
|
||||
runDaemonUninstall,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user