fix(plugins): stabilize registry package paths

This commit is contained in:
Vincent Koc
2026-04-25 12:33:00 -07:00
parent 958146bbac
commit d2046beb40
2 changed files with 39 additions and 3 deletions

View File

@@ -64,6 +64,7 @@ function createPluginCandidate(params: {
origin?: PluginCandidate["origin"];
packageName?: string;
packageVersion?: string;
packageDir?: string;
packageManifest?: OpenClawPackageManifest;
}): PluginCandidate {
return {
@@ -73,7 +74,7 @@ function createPluginCandidate(params: {
origin: params.origin ?? "global",
packageName: params.packageName,
packageVersion: params.packageVersion,
packageDir: params.rootDir,
packageDir: params.packageDir ?? params.rootDir,
packageManifest: params.packageManifest,
};
}
@@ -211,6 +212,33 @@ describe("installed plugin index", () => {
expect(contributions.contracts.get("tools")).toEqual(["demo"]);
});
it("keeps packageJson paths root-relative when packageDir is reached through a symlink", () => {
const fixture = createRichPluginFixture();
const linkParent = makeTempDir();
const linkRoot = path.join(linkParent, "linked-demo");
try {
fs.symlinkSync(fixture.rootDir, linkRoot, "dir");
} catch {
return;
}
const index = loadInstalledPluginIndex({
candidates: [
createPluginCandidate({
rootDir: fs.realpathSync(fixture.rootDir),
packageDir: linkRoot,
packageName: "@vendor/demo-plugin",
packageVersion: "1.2.3",
}),
],
env: hermeticEnv(),
});
expect(index.plugins[0]?.packageJson).toMatchObject({
path: "package.json",
});
});
it("exposes cold registry records and owners for existing plugins without install ledgers", () => {
const fixture = createRichPluginFixture();
const index = loadInstalledPluginIndex({

View File

@@ -19,6 +19,7 @@ import {
type PluginManifestRegistry,
} from "./manifest-registry.js";
import type { PluginDiagnostic } from "./manifest-types.js";
import { safeRealpathSync } from "./path-safety.js";
import { hasKind } from "./slots.js";
export const INSTALLED_PLUGIN_INDEX_VERSION = 1;
@@ -284,10 +285,17 @@ function resolvePackageJsonPath(candidate: PluginCandidate | undefined): string
if (!candidate?.packageDir) {
return undefined;
}
const packageJsonPath = path.join(candidate.packageDir, "package.json");
const packageDir = safeRealpathSync(candidate.packageDir) ?? path.resolve(candidate.packageDir);
const packageJsonPath = path.join(packageDir, "package.json");
return fs.existsSync(packageJsonPath) ? packageJsonPath : undefined;
}
function resolvePackageJsonRelativePath(rootDir: string, packageJsonPath: string): string {
const resolvedRootDir = safeRealpathSync(rootDir) ?? path.resolve(rootDir);
const relativePath = path.relative(resolvedRootDir, packageJsonPath) || "package.json";
return relativePath.split(path.sep).join("/");
}
function resolvePackageJsonRecord(params: {
candidate: PluginCandidate | undefined;
packageJsonPath: string | undefined;
@@ -307,7 +315,7 @@ function resolvePackageJsonRecord(params: {
return undefined;
}
return {
path: path.relative(params.candidate.rootDir, params.packageJsonPath) || "package.json",
path: resolvePackageJsonRelativePath(params.candidate.rootDir, params.packageJsonPath),
hash,
};
}