mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-04-30 14:02:56 +08:00
build: verify bundled plugin runtime mirrors in postpublish checks (#60112)
Merged via squash.
Prepared head SHA: 79bbb105a8
Co-authored-by: medns <1575008+medns@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
This commit is contained in:
@@ -158,6 +158,7 @@ Docs: https://docs.openclaw.ai
|
||||
- OpenShell/sandbox: pin verified file reads to an already-opened descriptor, walk the ancestor chain for symlinked parents on platforms without fd-path readlink, and re-check file identity so parent symlink swaps cannot redirect in-sandbox reads to host files outside the allowed mount root. (#69798) Thanks @drobison00.
|
||||
- Gateway/Control UI: require authenticated Control UI read access before serving `/__openclaw/control-ui-config.json` when `gateway.auth` is enabled, so unauthenticated callers can no longer read bootstrap metadata. (#70247) Thanks @drobison00.
|
||||
- Gateway/restart: default session-scoped restart sentinels to a one-shot agent continuation, so chat-initiated Gateway restarts acknowledge successful boot automatically. (#70269) Thanks @obviyus.
|
||||
- Build/npm publish: fail postpublish verification when root `dist/*` files import bundled plugin runtime dependencies without mirroring them in the root package manifest, so Slack-style plugin deps cannot silently ship on the wrong module-resolution path again. (#60112) thanks @medns.
|
||||
|
||||
## 2026.4.21
|
||||
|
||||
|
||||
@@ -151,6 +151,10 @@ function extractModuleSpecifiers(source) {
|
||||
return specifiers;
|
||||
}
|
||||
|
||||
function isPluginOwnedDistImporter(relativePath, pluginIds) {
|
||||
return pluginIds.some((pluginId) => relativePath.startsWith(`extensions/${pluginId}/`));
|
||||
}
|
||||
|
||||
export function collectRootDistBundledRuntimeMirrors(params) {
|
||||
const distDir = params.distDir;
|
||||
const bundledSpecs = params.bundledRuntimeDependencySpecs;
|
||||
@@ -177,6 +181,9 @@ export function collectRootDistBundledRuntimeMirrors(params) {
|
||||
continue;
|
||||
}
|
||||
const bundledSpec = bundledSpecs.get(dependencyName);
|
||||
if (isPluginOwnedDistImporter(relativePath, bundledSpec.pluginIds)) {
|
||||
continue;
|
||||
}
|
||||
const existing = mirrors.get(dependencyName);
|
||||
if (existing) {
|
||||
existing.importers.add(relativePath);
|
||||
@@ -195,6 +202,7 @@ export function collectRootDistBundledRuntimeMirrors(params) {
|
||||
|
||||
export function collectBundledPluginRootRuntimeMirrorErrors(params) {
|
||||
const errors = [];
|
||||
const declaredRootRuntimeDeps = collectRuntimeDependencySpecs(params.rootPackageJson);
|
||||
|
||||
for (const [dependencyName, record] of params.bundledRuntimeDependencySpecs) {
|
||||
for (const conflict of record.conflicts) {
|
||||
@@ -204,5 +212,17 @@ export function collectBundledPluginRootRuntimeMirrorErrors(params) {
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
for (const [dependencyName, record] of params.requiredRootMirrors) {
|
||||
if (declaredRootRuntimeDeps.has(dependencyName)) {
|
||||
continue;
|
||||
}
|
||||
const importerList = Array.from(record.importers)
|
||||
.toSorted((left, right) => left.localeCompare(right))
|
||||
.join(", ");
|
||||
errors.push(
|
||||
`installed package root is missing mirrored bundled runtime dependency '${dependencyName}' for dist importers: ${importerList}. Add it to package.json dependencies/optionalDependencies or keep imports under dist/extensions/${record.pluginIds[0]}/.`,
|
||||
);
|
||||
}
|
||||
|
||||
return errors.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
@@ -166,28 +166,49 @@ describe("collectInstalledMirroredRootDependencyManifestErrors", () => {
|
||||
writeFileSync(fullPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
function writeSlackWebApiProbePackage(
|
||||
root: string,
|
||||
dependencies: Record<string, string> = {},
|
||||
): void {
|
||||
writePackageFile(root, "package.json", {
|
||||
function writeSlackWebApiProbePackage(params: {
|
||||
root: string;
|
||||
importerPath?: string;
|
||||
rootDependencies?: Record<string, string>;
|
||||
rootOptionalDependencies?: Record<string, string>;
|
||||
}): void {
|
||||
writePackageFile(params.root, "package.json", {
|
||||
version: "2026.4.10",
|
||||
dependencies,
|
||||
dependencies: params.rootDependencies,
|
||||
optionalDependencies: params.rootOptionalDependencies,
|
||||
});
|
||||
writePackageFile(root, "dist/extensions/slack/package.json", {
|
||||
writePackageFile(params.root, "dist/extensions/slack/package.json", {
|
||||
dependencies: {
|
||||
"@slack/web-api": "^7.15.0",
|
||||
},
|
||||
});
|
||||
mkdirSync(join(root, "dist"), { recursive: true });
|
||||
writeFileSync(join(root, "dist", "probe-Cz2PiFtC.js"), 'import("@slack/web-api");\n', "utf8");
|
||||
const importerPath = params.importerPath ?? "dist/probe-Cz2PiFtC.js";
|
||||
mkdirSync(join(params.root, "dist"), { recursive: true });
|
||||
writeFileSync(join(params.root, importerPath), 'import("@slack/web-api");\n', "utf8");
|
||||
}
|
||||
|
||||
it("does not require root mirrors for bundled plugin deps imported by root dist", () => {
|
||||
it("flags bundled plugin deps imported by root dist when root mirrors are missing", () => {
|
||||
const packageRoot = makeInstalledPackageRoot();
|
||||
|
||||
try {
|
||||
writeSlackWebApiProbePackage(packageRoot);
|
||||
writeSlackWebApiProbePackage({ root: packageRoot });
|
||||
|
||||
expect(collectInstalledMirroredRootDependencyManifestErrors(packageRoot)).toEqual([
|
||||
"installed package root is missing mirrored bundled runtime dependency '@slack/web-api' for dist importers: probe-Cz2PiFtC.js. Add it to package.json dependencies/optionalDependencies or keep imports under dist/extensions/slack/.",
|
||||
]);
|
||||
} finally {
|
||||
rmSync(packageRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("allows bundled plugin deps imported from their own extension dist without root mirrors", () => {
|
||||
const packageRoot = makeInstalledPackageRoot();
|
||||
|
||||
try {
|
||||
writeSlackWebApiProbePackage({
|
||||
root: packageRoot,
|
||||
importerPath: "dist/extensions/slack/client-Cz2PiFtC.js",
|
||||
});
|
||||
|
||||
expect(collectInstalledMirroredRootDependencyManifestErrors(packageRoot)).toEqual([]);
|
||||
} finally {
|
||||
@@ -227,8 +248,11 @@ describe("collectInstalledMirroredRootDependencyManifestErrors", () => {
|
||||
const packageRoot = makeInstalledPackageRoot();
|
||||
|
||||
try {
|
||||
writeSlackWebApiProbePackage(packageRoot, {
|
||||
"@slack/web-api": "^7.16.0",
|
||||
writeSlackWebApiProbePackage({
|
||||
root: packageRoot,
|
||||
rootDependencies: {
|
||||
"@slack/web-api": "^7.16.0",
|
||||
},
|
||||
});
|
||||
|
||||
expect(collectInstalledMirroredRootDependencyManifestErrors(packageRoot)).toEqual([]);
|
||||
|
||||
Reference in New Issue
Block a user