fix: align postpublish verification with external plugins

(cherry picked from commit abe2b294ae)
This commit is contained in:
Peter Steinberger
2026-05-02 19:45:10 +01:00
parent f789f8e394
commit 422f1fe9bb
2 changed files with 93 additions and 25 deletions

View File

@@ -50,9 +50,16 @@ const PUBLISHED_BUNDLED_RUNTIME_SIDECAR_PATHS = BUNDLED_RUNTIME_SIDECAR_PATHS.fi
);
const NODE_BUILTIN_MODULES = new Set(builtinModules.map((name) => name.replace(/^node:/u, "")));
const MAX_INSTALLED_ROOT_PACKAGE_JSON_BYTES = 1024 * 1024;
const MAX_INSTALLED_ROOT_DIST_JS_BYTES = 2 * 1024 * 1024;
const MAX_INSTALLED_ROOT_DIST_JS_BYTES = 4 * 1024 * 1024;
const MAX_INSTALLED_ROOT_DIST_JS_FILES = 5000;
const ROOT_DIST_JAVASCRIPT_MODULE_FILE_RE = /\.(?:c|m)?js$/u;
const OPTIONAL_OR_EXTERNALIZED_RUNTIME_IMPORTS = new Set([
"@discordjs/opus",
"@lancedb/lancedb",
"@matrix-org/matrix-sdk-crypto-nodejs",
"link-preview-js",
"matrix-js-sdk",
]);
const require = createRequire(import.meta.url);
const acorn = require("acorn") as typeof import("acorn");
@@ -102,7 +109,7 @@ export function collectInstalledPackageErrors(params: {
);
}
for (const relativePath of PUBLISHED_BUNDLED_RUNTIME_SIDECAR_PATHS) {
for (const relativePath of collectInstalledBundledRuntimeSidecarPaths(params.packageRoot)) {
if (!existsSync(join(params.packageRoot, relativePath))) {
errors.push(`installed package is missing required bundled runtime sidecar: ${relativePath}`);
}
@@ -114,6 +121,31 @@ export function collectInstalledPackageErrors(params: {
return errors;
}
function collectInstalledBundledExtensionIds(packageRoot: string): Set<string> {
const extensionsDir = join(packageRoot, "dist", "extensions");
if (!existsSync(extensionsDir)) {
return new Set();
}
const ids = new Set<string>();
for (const entry of readdirSync(extensionsDir, { withFileTypes: true })) {
if (!entry.isDirectory()) {
continue;
}
if (existsSync(join(extensionsDir, entry.name, "package.json"))) {
ids.add(entry.name);
}
}
return ids;
}
export function collectInstalledBundledRuntimeSidecarPaths(packageRoot: string): string[] {
const installedExtensionIds = collectInstalledBundledExtensionIds(packageRoot);
return PUBLISHED_BUNDLED_RUNTIME_SIDECAR_PATHS.filter((relativePath) => {
const match = /^dist\/extensions\/([^/]+)\//u.exec(relativePath);
return match !== null && installedExtensionIds.has(match[1]);
});
}
export function normalizeInstalledBinaryVersion(output: string): string {
const trimmed = output.trim();
const versionMatch = /\b\d{4}\.\d{1,2}\.\d{1,2}(?:-\d+|-beta\.\d+)?\b/u.exec(trimmed);
@@ -304,6 +336,7 @@ export function collectInstalledRootDependencyManifestErrors(packageRoot: string
if (
!dependencyName ||
NODE_BUILTIN_MODULES.has(dependencyName) ||
OPTIONAL_OR_EXTERNALIZED_RUNTIME_IMPORTS.has(dependencyName) ||
declaredRuntimeDeps.has(dependencyName) ||
isBundledExtensionOwnedRuntimeImport({
dependencyName,

View File

@@ -2,24 +2,16 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { describe, expect, it } from "vitest";
import { listBundledPluginPackArtifacts } from "../scripts/lib/bundled-plugin-build-entries.mjs";
import {
buildPublishedInstallCommandArgs,
buildPublishedInstallScenarios,
collectInstalledBundledRuntimeSidecarPaths,
collectInstalledContextEngineRuntimeErrors,
collectInstalledRootDependencyManifestErrors,
collectInstalledPackageErrors,
normalizeInstalledBinaryVersion,
resolveInstalledBinaryPath,
} from "../scripts/openclaw-npm-postpublish-verify.ts";
import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../src/plugins/runtime-sidecar-paths.ts";
const PUBLISHED_BUNDLED_RUNTIME_SIDECAR_PATHS = BUNDLED_RUNTIME_SIDECAR_PATHS.filter(
(relativePath) => listBundledPluginPackArtifacts().includes(relativePath),
);
const REQUIRED_INSTALLED_RUNTIME_SIDECAR_PATHS = [
...PUBLISHED_BUNDLED_RUNTIME_SIDECAR_PATHS,
] as const;
describe("buildPublishedInstallScenarios", () => {
it("uses a single fresh scenario for plain stable releases", () => {
@@ -66,7 +58,11 @@ describe("buildPublishedInstallCommandArgs", () => {
});
describe("collectInstalledPackageErrors", () => {
it("flags version mismatches and missing runtime sidecars", () => {
function makeInstalledPackageRoot(): string {
return mkdtempSync(join(tmpdir(), "openclaw-postpublish-package-"));
}
it("flags version mismatches", () => {
const errors = collectInstalledPackageErrors({
expectedVersion: "2026.3.23-2",
installedVersion: "2026.3.23",
@@ -76,17 +72,35 @@ describe("collectInstalledPackageErrors", () => {
expect(errors[0]).toBe(
"installed package version mismatch: expected 2026.3.23-2, found 2026.3.23.",
);
expect(errors).toEqual(
expect.arrayContaining(
REQUIRED_INSTALLED_RUNTIME_SIDECAR_PATHS.map(
(relativePath) =>
`installed package is missing required bundled runtime sidecar: ${relativePath}`,
),
),
);
expect(errors.length).toBeGreaterThanOrEqual(
1 + REQUIRED_INSTALLED_RUNTIME_SIDECAR_PATHS.length,
);
});
it("requires runtime sidecars for bundled extensions included in the package", () => {
const packageRoot = makeInstalledPackageRoot();
try {
writeFileSync(join(packageRoot, "package.json"), '{"version":"2026.3.23"}\n', "utf8");
mkdirSync(join(packageRoot, "dist", "extensions", "slack"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "extensions", "slack", "package.json"),
"{}\n",
"utf8",
);
expect(collectInstalledBundledRuntimeSidecarPaths(packageRoot)).toContain(
"dist/extensions/slack/runtime-api.js",
);
expect(
collectInstalledPackageErrors({
expectedVersion: "2026.3.23",
installedVersion: "2026.3.23",
packageRoot,
}),
).toContain(
"installed package is missing required bundled runtime sidecar: dist/extensions/slack/runtime-api.js",
);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
});
@@ -212,6 +226,27 @@ describe("collectInstalledRootDependencyManifestErrors", () => {
}
});
it("accepts optional or externalized runtime imports", () => {
const packageRoot = makeInstalledPackageRoot();
try {
writePackageFile(packageRoot, "package.json", {
version: "2026.4.22",
dependencies: {},
});
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "optional-runtime.js"),
'await import("@lancedb/lancedb");\n',
"utf8",
);
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
it("flags undeclared imports from mjs and cjs root dist files", () => {
const packageRoot = makeInstalledPackageRoot();
@@ -318,12 +353,12 @@ describe("collectInstalledRootDependencyManifestErrors", () => {
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "oversized.js"),
"x".repeat(2 * 1024 * 1024 + 1),
"x".repeat(4 * 1024 * 1024 + 1),
"utf8",
);
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([
"installed package root dist file 'oversized.js' is invalid or exceeds 2097152 bytes.",
"installed package root dist file 'oversized.js' is invalid or exceeds 4194304 bytes.",
]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });