mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(plugins): preserve legacy runtime-doctor imports (#121220)
* fix(plugins): preserve legacy runtime doctor imports * fix(plugins): map legacy doctor package types
This commit is contained in:
@@ -89,7 +89,7 @@ Channel plugins should declare `doctorContract.stateMigrations: true` in
|
||||
`openclaw.plugin.json` and export `stateMigrations` from their doctor-contract
|
||||
artifact. Plan-based migrations can use
|
||||
`definePluginDoctorMigrationFromPlans(...)` from
|
||||
`openclaw/plugin-sdk/runtime-doctor` to preserve existing move, copy, preview,
|
||||
`openclaw/plugin-sdk/runtime-doctor-migrations` to preserve existing move, copy, preview,
|
||||
and plugin-state import behavior.
|
||||
|
||||
The setup-entry `legacyStateMigrations` option and feature flag,
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"openclaw/plugin-sdk/runtime-doctor-migrations": [
|
||||
"../packages/plugin-sdk/dist/src/plugin-sdk/runtime-doctor-migrations.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/runtime-doctor": [
|
||||
"../packages/plugin-sdk/dist/src/plugin-sdk/runtime-doctor.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/approval-reference-runtime": [
|
||||
"../packages/plugin-sdk/dist/src/plugin-sdk/approval-reference-runtime.d.ts"
|
||||
],
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"openclaw/plugin-sdk/runtime-doctor-migrations": [
|
||||
"../../packages/plugin-sdk/dist/src/plugin-sdk/runtime-doctor-migrations.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/runtime-doctor": [
|
||||
"../../packages/plugin-sdk/dist/src/plugin-sdk/runtime-doctor.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/approval-reference-runtime": [
|
||||
"../../packages/plugin-sdk/dist/src/plugin-sdk/approval-reference-runtime.d.ts"
|
||||
],
|
||||
|
||||
@@ -426,6 +426,9 @@
|
||||
"./plugin-sdk/plugin-state-store-runtime": {
|
||||
"default": "./dist/plugin-sdk/plugin-state-store-runtime.js"
|
||||
},
|
||||
"./plugin-sdk/runtime-doctor": {
|
||||
"default": "./dist/plugin-sdk/runtime-doctor.js"
|
||||
},
|
||||
"./plugin-sdk/runtime-doctor-migrations": {
|
||||
"default": "./dist/plugin-sdk/runtime-doctor-migrations.js"
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"secret-provider-alias",
|
||||
"session-store-paths",
|
||||
"plugin-state-store-runtime",
|
||||
"runtime-doctor",
|
||||
"runtime-doctor-migrations",
|
||||
"runtime-env",
|
||||
"proxy-capture",
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
"request-url",
|
||||
"response-limit-runtime",
|
||||
"retry-runtime",
|
||||
"runtime-doctor",
|
||||
"runtime-doctor-migrations",
|
||||
"runtime-fetch",
|
||||
"sandbox",
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
*
|
||||
* Doctor contract enumeration cold-loads plugin `doctor-contract-api` closures, so
|
||||
* this subpath must stay off heavy runtime graphs (state DB, plugin state stores,
|
||||
* uninstall flows). Those stay on `runtime-doctor`.
|
||||
* uninstall flows). Those stay on focused repair and plugin-state-store subpaths;
|
||||
* the deprecated `runtime-doctor` package facade re-exports only this light module.
|
||||
*/
|
||||
import fs from "node:fs/promises";
|
||||
import { asObjectRecord } from "../config/channel-compat-normalization.js";
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import * as runtimeDoctorMigrations from "./runtime-doctor-migrations.js";
|
||||
import * as legacyRuntimeDoctor from "./runtime-doctor.js";
|
||||
|
||||
describe("legacy runtime-doctor package facade", () => {
|
||||
it("is exactly the dependency-light migration surface", () => {
|
||||
expect(Object.keys(legacyRuntimeDoctor).toSorted()).toEqual(
|
||||
Object.keys(runtimeDoctorMigrations).toSorted(),
|
||||
);
|
||||
for (const key of Object.keys(runtimeDoctorMigrations)) {
|
||||
expect(legacyRuntimeDoctor[key as keyof typeof legacyRuntimeDoctor]).toBe(
|
||||
runtimeDoctorMigrations[key as keyof typeof runtimeDoctorMigrations],
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* @deprecated Package-only compatibility for pre-split official plugin doctor artifacts.
|
||||
* Current source must import `runtime-doctor-migrations` directly.
|
||||
*/
|
||||
export * from "./runtime-doctor-migrations.js";
|
||||
@@ -34,6 +34,15 @@ const FORBIDDEN_SPECIFIER_RULES = new Map<string, { reason: string; kinds: Set<C
|
||||
kinds: new Set(["doctor-contract", "legacy-setup"]),
|
||||
},
|
||||
],
|
||||
[
|
||||
"openclaw/plugin-sdk/runtime-doctor",
|
||||
{
|
||||
reason:
|
||||
"the retired package path exists only for shipped plugin artifacts; " +
|
||||
"current source must use openclaw/plugin-sdk/runtime-doctor-migrations",
|
||||
kinds: new Set(["doctor-contract", "legacy-setup"]),
|
||||
},
|
||||
],
|
||||
[
|
||||
"openclaw/plugin-sdk/ssrf-runtime",
|
||||
{
|
||||
|
||||
@@ -137,6 +137,48 @@ module.exports = {
|
||||
);
|
||||
}
|
||||
|
||||
function writeLegacyRuntimeDoctorPlugin(params: {
|
||||
pluginRoot: string;
|
||||
pluginId: string;
|
||||
importedSymbols: readonly string[];
|
||||
}): void {
|
||||
fs.mkdirSync(path.join(params.pluginRoot, "dist"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(params.pluginRoot, "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
id: params.pluginId,
|
||||
doctorContract: { configRepair: true },
|
||||
configSchema: {},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(params.pluginRoot, "package.json"),
|
||||
JSON.stringify({
|
||||
name: `@openclaw/${params.pluginId}`,
|
||||
version: "2026.7.2-beta.7",
|
||||
type: "module",
|
||||
openclaw: { extensions: ["./dist/index.js"] },
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(path.join(params.pluginRoot, "dist", "index.js"), "export {};\n", "utf8");
|
||||
fs.writeFileSync(
|
||||
path.join(params.pluginRoot, "dist", "doctor-contract-api.js"),
|
||||
`import { ${params.importedSymbols.join(", ")} } from "openclaw/plugin-sdk/runtime-doctor";
|
||||
const importedHelpers = [${params.importedSymbols.join(", ")}];
|
||||
if (importedHelpers.some((helper) => typeof helper !== "function")) {
|
||||
throw new Error("legacy runtime-doctor helper missing");
|
||||
}
|
||||
export const legacyConfigRules = [{
|
||||
path: ["plugins", "entries", ${JSON.stringify(params.pluginId)}, "config", "legacyDoctor"],
|
||||
message: ${JSON.stringify(`${params.pluginId} legacy doctor contract loaded`)},
|
||||
}];
|
||||
`,
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
|
||||
function writeLegacyChannelMigrationPlugin(params: {
|
||||
pluginRoot: string;
|
||||
pluginId: string;
|
||||
@@ -531,6 +573,48 @@ describe("doctor contract registry load-path plugins", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
pluginId: "clickclack-legacy-doctor",
|
||||
importedSymbols: ["asObjectRecord"],
|
||||
},
|
||||
{
|
||||
pluginId: "codex-legacy-doctor",
|
||||
importedSymbols: ["archiveLegacyStateSource", "legacyStateFileExists"],
|
||||
},
|
||||
{
|
||||
pluginId: "discord-legacy-doctor",
|
||||
importedSymbols: [
|
||||
"asObjectRecord",
|
||||
"collectChannelAccountScopes",
|
||||
"collectProviderDangerousNameMatchingScopes",
|
||||
"defineChannelAliasMigration",
|
||||
"defineKeyMoveMigration",
|
||||
"hasLegacyAccountStreamingAliases",
|
||||
"normalizeChannelAccounts",
|
||||
"stripRetiredChannelKeys",
|
||||
],
|
||||
},
|
||||
])("loads the preserved $pluginId package contract", ({ pluginId, importedSymbols }) => {
|
||||
const stateDir = tempDirs.make("openclaw-doctor-contract-legacy-package-");
|
||||
const pluginRoot = tempDirs.make("openclaw-doctor-contract-legacy-package-");
|
||||
writeLegacyRuntimeDoctorPlugin({ pluginRoot, pluginId, importedSymbols });
|
||||
const config = createDoctorPluginConfig(pluginRoot, pluginId);
|
||||
|
||||
expect(
|
||||
listPluginDoctorLegacyConfigRules({
|
||||
config,
|
||||
env: makeHermeticDoctorEnv(stateDir),
|
||||
pluginIds: [pluginId],
|
||||
}),
|
||||
).toEqual([
|
||||
{
|
||||
path: ["plugins", "entries", pluginId, "config", "legacyDoctor"],
|
||||
message: `${pluginId} legacy doctor contract loaded`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("applies compatibility normalizers from plugins.load.paths", () => {
|
||||
const stateDir = tempDirs.make("openclaw-doctor-contract-load-paths-");
|
||||
const pluginRoot = tempDirs.make("openclaw-doctor-contract-load-paths-");
|
||||
|
||||
Reference in New Issue
Block a user