mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
Keep legacy plugin manifest doctor check lint-only
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
||||
collectLegacyPluginManifestContractMigrations,
|
||||
legacyPluginManifestContractMigrationToHealthFinding,
|
||||
maybeRepairLegacyPluginManifestContracts,
|
||||
repairLegacyPluginManifestContractFindings,
|
||||
} from "./doctor-plugin-manifests.js";
|
||||
import type { DoctorPrompter } from "./doctor-prompter.js";
|
||||
|
||||
@@ -281,63 +280,6 @@ describe("doctor plugin manifest legacy contract repair", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("previews legacy manifest rewrites as file effects and diffs", async () => {
|
||||
const pluginsRoot = await suiteTempDirs.make("preview-capability");
|
||||
const root = path.join(pluginsRoot, "openai");
|
||||
fs.mkdirSync(root, { recursive: true });
|
||||
writePackageJson(root);
|
||||
writeManifest(root, {
|
||||
id: "openai",
|
||||
speechProviders: ["openai"],
|
||||
configSchema: { type: "object" },
|
||||
});
|
||||
const manifestPath = path.join(root, "openclaw.plugin.json");
|
||||
|
||||
const result = await repairLegacyPluginManifestContractFindings({
|
||||
config: configWithPluginLoadPath(pluginsRoot),
|
||||
env: {
|
||||
...process.env,
|
||||
},
|
||||
manifestRoots: [pluginsRoot],
|
||||
findings: [
|
||||
{
|
||||
checkId: "core/doctor/legacy-plugin-manifests",
|
||||
severity: "warning",
|
||||
message: "Plugin manifest openai uses legacy top-level capability keys.",
|
||||
path: manifestPath,
|
||||
target: "openai",
|
||||
requirement: "contracts-capability-keys",
|
||||
},
|
||||
],
|
||||
dryRun: true,
|
||||
diff: true,
|
||||
});
|
||||
|
||||
expect(result.changes).toEqual([
|
||||
`- ${manifestPath}: moved speechProviders to contracts.speechProviders`,
|
||||
]);
|
||||
expect(result.effects).toEqual([
|
||||
{
|
||||
kind: "file",
|
||||
action: "would-rewrite-legacy-plugin-manifest-contracts",
|
||||
target: manifestPath,
|
||||
dryRunSafe: false,
|
||||
},
|
||||
]);
|
||||
expect(result.diffs).toEqual([
|
||||
expect.objectContaining({
|
||||
kind: "file",
|
||||
path: manifestPath,
|
||||
before: expect.stringContaining('"speechProviders"'),
|
||||
after: expect.stringContaining('"contracts"'),
|
||||
}),
|
||||
]);
|
||||
const unchanged = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as {
|
||||
speechProviders?: string[];
|
||||
};
|
||||
expect(unchanged.speechProviders).toEqual(["openai"]);
|
||||
});
|
||||
|
||||
it("ignores non-object contracts payloads when collecting migrations", async () => {
|
||||
const pluginsRoot = await suiteTempDirs.make("non-object-contracts");
|
||||
const root = path.join(pluginsRoot, "openai");
|
||||
|
||||
@@ -6,12 +6,7 @@ import { normalizeTrimmedStringList } from "@openclaw/normalization-core/string-
|
||||
import { z } from "zod";
|
||||
import { note } from "../../packages/terminal-core/src/note.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type {
|
||||
HealthFinding,
|
||||
HealthRepairDiff,
|
||||
HealthRepairEffect,
|
||||
HealthRepairResult,
|
||||
} from "../flows/health-checks.js";
|
||||
import type { HealthFinding } from "../flows/health-checks.js";
|
||||
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
@@ -178,132 +173,6 @@ function migrationToManifestJson(migration: LegacyManifestContractMigration): st
|
||||
return `${JSON.stringify(migration.nextRaw, null, 2)}\n`;
|
||||
}
|
||||
|
||||
function legacyPluginManifestMigrationToRepairEffect(
|
||||
migration: LegacyManifestContractMigration,
|
||||
dryRun: boolean,
|
||||
): HealthRepairEffect {
|
||||
return {
|
||||
kind: "file",
|
||||
action: dryRun
|
||||
? "would-rewrite-legacy-plugin-manifest-contracts"
|
||||
: "rewrite-legacy-plugin-manifest-contracts",
|
||||
target: migration.manifestPath,
|
||||
dryRunSafe: false,
|
||||
};
|
||||
}
|
||||
|
||||
function legacyPluginManifestMigrationToRepairDiff(
|
||||
migration: LegacyManifestContractMigration,
|
||||
before: string,
|
||||
): HealthRepairDiff {
|
||||
return {
|
||||
kind: "file",
|
||||
path: migration.manifestPath,
|
||||
before,
|
||||
after: migrationToManifestJson(migration),
|
||||
};
|
||||
}
|
||||
|
||||
/** Repairs or previews selected legacy plugin manifest contract migrations. */
|
||||
export async function repairLegacyPluginManifestContractFindings(params: {
|
||||
config?: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
manifestRoots?: string[];
|
||||
workspaceDir?: string;
|
||||
findings: readonly HealthFinding[];
|
||||
dryRun?: boolean;
|
||||
diff?: boolean;
|
||||
deps?: {
|
||||
readFileSync?: typeof fs.readFileSync;
|
||||
writeFileSync?: typeof fs.writeFileSync;
|
||||
};
|
||||
}): Promise<HealthRepairResult> {
|
||||
const selectedPaths = new Set(
|
||||
params.findings
|
||||
.filter(
|
||||
(finding) =>
|
||||
finding.checkId === LEGACY_PLUGIN_MANIFESTS_CHECK_ID &&
|
||||
finding.requirement === "contracts-capability-keys" &&
|
||||
typeof finding.path === "string",
|
||||
)
|
||||
.map((finding) => finding.path as string),
|
||||
);
|
||||
if (selectedPaths.size === 0) {
|
||||
return {
|
||||
status: "skipped",
|
||||
reason: "no repairable legacy plugin manifest findings were present",
|
||||
changes: [],
|
||||
};
|
||||
}
|
||||
|
||||
const migrations = collectLegacyPluginManifestContractMigrations({
|
||||
...(params.config ? { config: params.config } : {}),
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
...(params.manifestRoots ? { manifestRoots: params.manifestRoots } : {}),
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
}).filter((migration) => selectedPaths.has(migration.manifestPath));
|
||||
if (migrations.length === 0) {
|
||||
return {
|
||||
status: "skipped",
|
||||
reason: "selected legacy plugin manifests no longer need repair",
|
||||
changes: [],
|
||||
};
|
||||
}
|
||||
|
||||
const readFile = params.deps?.readFileSync ?? fs.readFileSync;
|
||||
const writeFile = params.deps?.writeFileSync ?? fs.writeFileSync;
|
||||
const changes: string[] = [];
|
||||
const diffs: HealthRepairDiff[] = [];
|
||||
const effects: HealthRepairEffect[] = [];
|
||||
const warnings: string[] = [];
|
||||
|
||||
for (const migration of migrations) {
|
||||
let before: string | undefined;
|
||||
if (params.diff === true) {
|
||||
try {
|
||||
before = readFile(migration.manifestPath, "utf-8");
|
||||
diffs.push(legacyPluginManifestMigrationToRepairDiff(migration, before));
|
||||
} catch (error) {
|
||||
warnings.push(
|
||||
`Could not read legacy plugin manifest at ${migration.manifestPath}: ${String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (params.dryRun !== true) {
|
||||
try {
|
||||
writeFile(migration.manifestPath, migrationToManifestJson(migration), "utf-8");
|
||||
} catch (error) {
|
||||
warnings.push(
|
||||
`Failed to rewrite legacy plugin manifest at ${migration.manifestPath}: ${String(error)}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
changes.push(...migration.changeLines);
|
||||
effects.push(legacyPluginManifestMigrationToRepairEffect(migration, params.dryRun === true));
|
||||
}
|
||||
|
||||
if (changes.length === 0 && warnings.length > 0) {
|
||||
return {
|
||||
status: "failed",
|
||||
reason: "could not rewrite selected legacy plugin manifests",
|
||||
changes,
|
||||
warnings,
|
||||
diffs,
|
||||
effects,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
changes,
|
||||
warnings,
|
||||
diffs,
|
||||
effects,
|
||||
};
|
||||
}
|
||||
|
||||
/** Prompts and rewrites legacy plugin manifest contract fields when doctor repair is enabled. */
|
||||
export async function maybeRepairLegacyPluginManifestContracts(params: {
|
||||
config?: OpenClawConfig;
|
||||
|
||||
@@ -69,10 +69,6 @@ const mocks = vi.hoisted(() => ({
|
||||
}),
|
||||
),
|
||||
maybeRepairLegacyPluginManifestContracts: vi.fn().mockResolvedValue(undefined),
|
||||
repairLegacyPluginManifestContractFindings: vi.fn(async () => ({
|
||||
changes: [] as string[],
|
||||
effects: [] as unknown[],
|
||||
})),
|
||||
detectLegacyClawdBrowserProfileResidue: vi.fn(),
|
||||
maybeArchiveLegacyClawdBrowserProfileResidue: vi.fn(),
|
||||
resolveAgentWorkspaceDir: vi.fn(() => "/tmp/openclaw-workspace"),
|
||||
@@ -179,7 +175,6 @@ vi.mock("../commands/doctor-plugin-manifests.js", () => ({
|
||||
legacyPluginManifestContractMigrationToHealthFinding:
|
||||
mocks.legacyPluginManifestContractMigrationToHealthFinding,
|
||||
maybeRepairLegacyPluginManifestContracts: mocks.maybeRepairLegacyPluginManifestContracts,
|
||||
repairLegacyPluginManifestContractFindings: mocks.repairLegacyPluginManifestContractFindings,
|
||||
}));
|
||||
|
||||
vi.mock("../commands/doctor-auth-oauth-sidecar.js", () => ({
|
||||
@@ -390,11 +385,6 @@ describe("doctor health contributions", () => {
|
||||
mocks.legacyPluginManifestContractMigrationToHealthFinding.mockClear();
|
||||
mocks.maybeRepairLegacyPluginManifestContracts.mockClear();
|
||||
mocks.maybeRepairLegacyPluginManifestContracts.mockResolvedValue(undefined);
|
||||
mocks.repairLegacyPluginManifestContractFindings.mockClear();
|
||||
mocks.repairLegacyPluginManifestContractFindings.mockResolvedValue({
|
||||
changes: [],
|
||||
effects: [],
|
||||
});
|
||||
mocks.maybeRepairLegacyOAuthSidecarProfiles.mockClear();
|
||||
mocks.maybeRepairLegacyOAuthSidecarProfiles.mockResolvedValue(undefined);
|
||||
mocks.collectAuthProfileHealthFindings.mockClear();
|
||||
@@ -566,56 +556,6 @@ describe("doctor health contributions", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("threads dry-run legacy plugin manifest repairs through the structured check", async () => {
|
||||
const contribution = requireDoctorContribution("doctor:legacy-plugin-manifests");
|
||||
const check = contribution.healthChecks[0] as HealthCheck;
|
||||
const findings = [
|
||||
{
|
||||
checkId: "core/doctor/legacy-plugin-manifests",
|
||||
severity: "warning" as const,
|
||||
message: "Plugin manifest legacy-plugin uses legacy top-level capability keys.",
|
||||
path: "/tmp/openclaw-plugin/openclaw.plugin.json",
|
||||
target: "legacy-plugin",
|
||||
requirement: "contracts-capability-keys",
|
||||
},
|
||||
];
|
||||
mocks.repairLegacyPluginManifestContractFindings.mockResolvedValueOnce({
|
||||
changes: ["Would rewrite legacy manifest."],
|
||||
effects: [
|
||||
{
|
||||
kind: "file",
|
||||
action: "would-rewrite-legacy-plugin-manifest-contracts",
|
||||
target: "/tmp/openclaw-plugin/openclaw.plugin.json",
|
||||
dryRunSafe: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = await check.repair?.(
|
||||
{
|
||||
cfg: { plugins: { load: { paths: ["/tmp/openclaw-plugin"] } } },
|
||||
mode: "fix",
|
||||
dryRun: true,
|
||||
diff: true,
|
||||
runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
|
||||
},
|
||||
findings,
|
||||
);
|
||||
|
||||
expect(mocks.repairLegacyPluginManifestContractFindings).toHaveBeenCalledWith({
|
||||
config: { plugins: { load: { paths: ["/tmp/openclaw-plugin"] } } },
|
||||
env: process.env,
|
||||
findings,
|
||||
dryRun: true,
|
||||
diff: true,
|
||||
});
|
||||
expect(result?.effects).toContainEqual(
|
||||
expect.objectContaining({
|
||||
action: "would-rewrite-legacy-plugin-manifest-contracts",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("runs release configured plugin install repair before plugin registry and final config writes", () => {
|
||||
const ids = resolveDoctorHealthContributions().map((entry) => entry.id);
|
||||
|
||||
|
||||
@@ -1353,17 +1353,6 @@ export function resolveDoctorHealthContributions(): DoctorHealthContribution[] {
|
||||
env: process.env,
|
||||
}).map(legacyPluginManifestContractMigrationToHealthFinding);
|
||||
},
|
||||
async repair(ctx, findings) {
|
||||
const { repairLegacyPluginManifestContractFindings } =
|
||||
await import("../commands/doctor-plugin-manifests.js");
|
||||
return await repairLegacyPluginManifestContractFindings({
|
||||
config: ctx.cfg,
|
||||
env: process.env,
|
||||
findings,
|
||||
dryRun: ctx.dryRun,
|
||||
diff: ctx.diff,
|
||||
});
|
||||
},
|
||||
},
|
||||
run: runLegacyPluginManifestHealth,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user