





















@@ -1,3 +1,4 @@
1+import crypto from "node:crypto";
12import fs from "node:fs";
23import path from "node:path";
34import { afterEach, describe, expect, it, vi } from "vitest";
@@ -59,6 +60,19 @@ function replaceFilePreservingSizeAndMtime(filePath: string, contents: string) {
5960fs.utimesSync(filePath, previous.atime, previous.mtime);
6061}
616263+function fileHash(filePath: string): string {
64+return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
65+}
66+67+function fileSignature(filePath: string) {
68+const stat = fs.statSync(filePath);
69+return {
70+size: stat.size,
71+mtimeMs: stat.mtimeMs,
72+ctimeMs: stat.ctimeMs,
73+};
74+}
75+6276function createManifestlessClaudeBundleIndex(params: {
6377rootDir: string;
6478env: NodeJS.ProcessEnv;
@@ -206,6 +220,54 @@ describe("loadPluginRegistrySnapshotWithMetadata", () => {
206220expect(result.diagnostics).toStrictEqual([]);
207221});
208222223+it("keeps persisted package plugins with dot-prefixed package metadata paths", () => {
224+const tempRoot = makeTempDir();
225+const rootDir = path.join(tempRoot, "workspace");
226+const stateDir = path.join(tempRoot, "state");
227+const env = { ...createHermeticEnv(tempRoot), OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1" };
228+const config = {
229+plugins: {
230+load: { paths: [rootDir] },
231+},
232+};
233+writePackagePlugin(rootDir);
234+const metaDir = path.join(rootDir, "..meta");
235+fs.mkdirSync(metaDir, { recursive: true });
236+const packageJsonPath = path.join(metaDir, "package.json");
237+fs.writeFileSync(packageJsonPath, JSON.stringify({ name: "demo", version: "1.0.0" }), "utf8");
238+const index = loadInstalledPluginIndex({ config, env });
239+const [plugin] = index.plugins;
240+if (!plugin) {
241+throw new Error("expected test plugin");
242+}
243+writePersistedInstalledPluginIndexSync(
244+{
245+ ...index,
246+plugins: [
247+{
248+ ...plugin,
249+packageJson: {
250+path: "..meta/package.json",
251+hash: fileHash(packageJsonPath),
252+fileSignature: fileSignature(packageJsonPath),
253+},
254+},
255+ ...index.plugins.slice(1),
256+],
257+},
258+{ stateDir },
259+);
260+261+const result = loadPluginRegistrySnapshotWithMetadata({
262+ config,
263+ env,
264+ stateDir,
265+});
266+267+expect(result.source).toBe("persisted");
268+expect(result.diagnostics).toStrictEqual([]);
269+});
270+209271it("detects same-size same-mtime manifest replacements", () => {
210272const tempRoot = makeTempDir();
211273const rootDir = path.join(tempRoot, "workspace");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。