mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-20 01:21:41 -06:00
0fe49c5731
* fix(plugins): preserve retained uninstall files * test(plugins): track retention temp dirs * fix(plugins): retain rollback marker snapshots
142 lines
5.2 KiB
TypeScript
142 lines
5.2 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
|
import { withOpenClawTestState } from "../test-utils/openclaw-test-state.js";
|
|
import { commitPluginInstallRecordsWithConfig } from "./install-record-commit.js";
|
|
import { listRecoveredManagedNpmInstallCandidates } from "./installed-plugin-index-record-reader.js";
|
|
import {
|
|
cleanupRetainedManagedNpmInstallGenerations,
|
|
hasRetainedManagedNpmInstallMarker,
|
|
} from "./managed-npm-retention.js";
|
|
import { writeManagedNpmPlugin } from "./test-helpers/managed-npm-plugin.js";
|
|
|
|
function npmRecord(packageName: string, installPath: string): PluginInstallRecord {
|
|
return { source: "npm", spec: `${packageName}@1.0.0`, installPath };
|
|
}
|
|
|
|
describe("retained managed npm record commits", () => {
|
|
it("suppresses recovery when a retained install record is removed", async () => {
|
|
await withOpenClawTestState({ label: "retained-record-removal" }, async (state) => {
|
|
const packageName = "@openclaw/retained-demo";
|
|
const installPath = writeManagedNpmPlugin({
|
|
stateDir: state.stateDir,
|
|
packageName,
|
|
pluginId: "retained-demo",
|
|
version: "1.0.0",
|
|
});
|
|
expect(
|
|
listRecoveredManagedNpmInstallCandidates({ stateDir: state.stateDir }).map(
|
|
(candidate) => candidate.pluginId,
|
|
),
|
|
).toContain("retained-demo");
|
|
|
|
await commitPluginInstallRecordsWithConfig({
|
|
previousInstallRecords: { "retained-demo": npmRecord(packageName, installPath) },
|
|
nextInstallRecords: {},
|
|
nextConfig: {},
|
|
});
|
|
|
|
expect(hasRetainedManagedNpmInstallMarker(installPath)).toBe(true);
|
|
expect(
|
|
listRecoveredManagedNpmInstallCandidates({ stateDir: state.stateDir }).map(
|
|
(candidate) => candidate.pluginId,
|
|
),
|
|
).not.toContain("retained-demo");
|
|
});
|
|
});
|
|
|
|
it.each(["direct", "symlink"] as const)(
|
|
"does not retire a package still used by a %s active install path",
|
|
async (activePathKind) => {
|
|
await withOpenClawTestState({ label: `retained-active-${activePathKind}` }, async (state) => {
|
|
const packageName = "@openclaw/retained-active";
|
|
const installPath = writeManagedNpmPlugin({
|
|
stateDir: state.stateDir,
|
|
packageName,
|
|
pluginId: "retained-active",
|
|
version: "1.0.0",
|
|
});
|
|
let activePath = installPath;
|
|
if (activePathKind === "symlink") {
|
|
activePath = state.statePath("active", "retained-active");
|
|
fs.mkdirSync(path.dirname(activePath), { recursive: true });
|
|
fs.symlinkSync(installPath, activePath, "dir");
|
|
}
|
|
|
|
await commitPluginInstallRecordsWithConfig({
|
|
previousInstallRecords: { "retained-active": npmRecord(packageName, installPath) },
|
|
nextInstallRecords: {
|
|
"active-alias": {
|
|
source: "path",
|
|
sourcePath: activePath,
|
|
installPath: activePath,
|
|
},
|
|
},
|
|
nextConfig: {},
|
|
});
|
|
|
|
expect(hasRetainedManagedNpmInstallMarker(installPath)).toBe(false);
|
|
});
|
|
},
|
|
);
|
|
|
|
it("does not retire a removed npm record outside the managed npm root", async () => {
|
|
await withOpenClawTestState({ label: "retained-outside-root" }, async (state) => {
|
|
const outsideRoot = state.path("outside-root");
|
|
try {
|
|
const packageName = "@openclaw/outside-retained";
|
|
const installPath = writeManagedNpmPlugin({
|
|
stateDir: outsideRoot,
|
|
packageName,
|
|
pluginId: "outside-retained",
|
|
version: "1.0.0",
|
|
});
|
|
await commitPluginInstallRecordsWithConfig({
|
|
previousInstallRecords: { "outside-retained": npmRecord(packageName, installPath) },
|
|
nextInstallRecords: {},
|
|
nextConfig: {},
|
|
});
|
|
expect(hasRetainedManagedNpmInstallMarker(installPath)).toBe(false);
|
|
} finally {
|
|
fs.rmSync(outsideRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
it("keeps npm-to-local source changes cleanup-eligible", async () => {
|
|
await withOpenClawTestState({ label: "retained-source-change" }, async (state) => {
|
|
const packageName = "@openclaw/moved-local";
|
|
const installPath = writeManagedNpmPlugin({
|
|
stateDir: state.stateDir,
|
|
packageName,
|
|
pluginId: "moved-local",
|
|
version: "1.0.0",
|
|
});
|
|
const localInstallPath = state.statePath("extensions", "moved-local");
|
|
fs.mkdirSync(localInstallPath, { recursive: true });
|
|
|
|
await commitPluginInstallRecordsWithConfig({
|
|
previousInstallRecords: { "moved-local": npmRecord(packageName, installPath) },
|
|
nextInstallRecords: {
|
|
"moved-local": {
|
|
source: "path",
|
|
sourcePath: localInstallPath,
|
|
installPath: localInstallPath,
|
|
},
|
|
},
|
|
nextConfig: {},
|
|
});
|
|
|
|
expect(hasRetainedManagedNpmInstallMarker(installPath)).toBe(true);
|
|
await expect(
|
|
cleanupRetainedManagedNpmInstallGenerations({
|
|
activeInstallPaths: [localInstallPath],
|
|
}),
|
|
).resolves.toBe(1);
|
|
expect(fs.existsSync(installPath)).toBe(false);
|
|
expect(fs.existsSync(localInstallPath)).toBe(true);
|
|
});
|
|
});
|
|
});
|