mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-02 11:04:54 +00:00
fix(update): prune stale local bundled plugin shadows
Summary:\n- prune stale local bundled plugin path records during update/doctor repair\n- keep current, same-version, versionless, source-checkout, and arbitrary local path records preserved\n- add changelog and deterministic sort comparator cleanup\n\nVerification:\n- node scripts/run-vitest.mjs src/plugins/contracts/boundary-invariants.test.ts src/plugins/stale-local-bundled-plugin-install-records.test.ts src/cli/update-cli/post-core-plugin-convergence.test.ts src/commands/doctor-plugin-registry.test.ts\n- node scripts/run-oxlint-shards.mjs --threads=8\n- ./node_modules/.bin/oxfmt --check --threads=1 CHANGELOG.md src/plugins/stale-local-bundled-plugin-install-records.ts src/commands/doctor-plugin-registry.ts\n- git diff --check\n- GitHub exact-SHA: Real behavior proof, build-artifacts, checks-fast-contracts-plugins-a, check-prod-types, check-lint, check-test-types green on 8bcbf681ec
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
repairMissingConfiguredPluginInstalls: vi.fn(),
|
||||
@@ -21,6 +24,8 @@ import {
|
||||
} from "./post-core-plugin-convergence.js";
|
||||
|
||||
describe("runPostCorePluginConvergence", () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.repairMissingConfiguredPluginInstalls.mockResolvedValue({
|
||||
@@ -31,6 +36,43 @@ describe("runPostCorePluginConvergence", () => {
|
||||
mocks.runPluginPayloadSmokeCheck.mockResolvedValue({ checked: [], failures: [] });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function makeTempDir(): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-post-core-convergence-"));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
function writeBundledPlugin(rootDir: string, pluginId: string): string {
|
||||
const pluginDir = path.join(rootDir, pluginId);
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(pluginDir, "index.js"), "export default {};\n", "utf8");
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
id: pluginId,
|
||||
name: pluginId,
|
||||
version: "2026.5.20-beta.1",
|
||||
configSchema: { type: "object" },
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: `@openclaw/${pluginId}`,
|
||||
version: "2026.5.20-beta.1",
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
return pluginDir;
|
||||
}
|
||||
|
||||
it("calls repair with OPENCLAW_UPDATE_POST_CORE_CONVERGENCE=1 set", async () => {
|
||||
const cfg = { plugins: { entries: {} } } as unknown as OpenClawConfig;
|
||||
await runPostCorePluginConvergence({
|
||||
@@ -121,6 +163,55 @@ describe("runPostCorePluginConvergence", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("prunes stale local bundled plugin shadows from baseline records before repair", async () => {
|
||||
const bundledRoot = makeTempDir();
|
||||
writeBundledPlugin(bundledRoot, "discord");
|
||||
const baseline = {
|
||||
discord: {
|
||||
source: "path" as const,
|
||||
installPath: path.join(makeTempDir(), "dist", "extensions", "discord"),
|
||||
version: "2026.5.4-beta.3",
|
||||
},
|
||||
brave: { source: "npm" as const, installPath: "/p/brave" },
|
||||
};
|
||||
mocks.repairMissingConfiguredPluginInstalls.mockResolvedValue({
|
||||
changes: [],
|
||||
warnings: [],
|
||||
records: { brave: baseline.brave },
|
||||
});
|
||||
const cfg = {
|
||||
plugins: { entries: { discord: { enabled: true }, brave: { enabled: true } } },
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const result = await runPostCorePluginConvergence({
|
||||
cfg,
|
||||
env: {
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot,
|
||||
OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1",
|
||||
VITEST: "true",
|
||||
},
|
||||
baselineInstallRecords: baseline,
|
||||
});
|
||||
|
||||
expect(mocks.repairMissingConfiguredPluginInstalls).toHaveBeenCalledWith({
|
||||
cfg,
|
||||
env: {
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot,
|
||||
OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1",
|
||||
VITEST: "true",
|
||||
OPENCLAW_COMPATIBILITY_HOST_VERSION: VERSION,
|
||||
OPENCLAW_UPDATE_POST_CORE_CONVERGENCE: "1",
|
||||
},
|
||||
baselineRecords: {
|
||||
brave: baseline.brave,
|
||||
},
|
||||
});
|
||||
expect(result.changes).toEqual([
|
||||
'Removed stale local bundled plugin install record "discord".',
|
||||
]);
|
||||
expect(result.installRecords).toEqual({ brave: baseline.brave });
|
||||
});
|
||||
|
||||
it("flags errored=true and surfaces actionable guidance when repair warns", async () => {
|
||||
mocks.repairMissingConfiguredPluginInstalls.mockResolvedValue({
|
||||
changes: [],
|
||||
|
||||
@@ -3,6 +3,7 @@ import { UPDATE_POST_CORE_CONVERGENCE_ENV } from "../../commands/doctor/shared/u
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { PluginInstallRecord } from "../../config/types.plugins.js";
|
||||
import { normalizePluginsConfig, resolveEffectiveEnableState } from "../../plugins/config-state.js";
|
||||
import { pruneStaleLocalBundledPluginInstallRecords } from "../../plugins/stale-local-bundled-plugin-install-records.js";
|
||||
import {
|
||||
resolveTrustedSourceLinkedOfficialClawHubSpec,
|
||||
resolveTrustedSourceLinkedOfficialNpmSpec,
|
||||
@@ -66,11 +67,17 @@ export async function runPostCorePluginConvergence(params: {
|
||||
OPENCLAW_COMPATIBILITY_HOST_VERSION: VERSION,
|
||||
[UPDATE_POST_CORE_CONVERGENCE_ENV]: "1",
|
||||
};
|
||||
const prunedBaseline = params.baselineInstallRecords
|
||||
? pruneStaleLocalBundledPluginInstallRecords({
|
||||
installRecords: params.baselineInstallRecords,
|
||||
env,
|
||||
})
|
||||
: null;
|
||||
|
||||
const repair = await repairMissingConfiguredPluginInstalls({
|
||||
cfg: params.cfg,
|
||||
env,
|
||||
...(params.baselineInstallRecords ? { baselineRecords: params.baselineInstallRecords } : {}),
|
||||
...(prunedBaseline ? { baselineRecords: prunedBaseline.records } : {}),
|
||||
});
|
||||
|
||||
const warnings: PostCoreConvergenceWarning[] = repair.warnings.map((message) => ({
|
||||
@@ -99,7 +106,12 @@ export async function runPostCorePluginConvergence(params: {
|
||||
}
|
||||
|
||||
return {
|
||||
changes: repair.changes,
|
||||
changes: [
|
||||
...(prunedBaseline?.stale.map(
|
||||
(record) => `Removed stale local bundled plugin install record "${record.pluginId}".`,
|
||||
) ?? []),
|
||||
...repair.changes,
|
||||
],
|
||||
warnings,
|
||||
errored: warnings.length > 0,
|
||||
smokeFailures: smoke.failures,
|
||||
|
||||
Reference in New Issue
Block a user