From abe2b294ae69dad0c6f84e927526aaca78853ba4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 19:45:10 +0100 Subject: [PATCH] fix: align postpublish verification with external plugins --- scripts/openclaw-npm-postpublish-verify.ts | 37 ++++++++- test/openclaw-npm-postpublish-verify.test.ts | 81 ++++++++++++++------ 2 files changed, 93 insertions(+), 25 deletions(-) diff --git a/scripts/openclaw-npm-postpublish-verify.ts b/scripts/openclaw-npm-postpublish-verify.ts index 4aa11c1257e..41563fbc5f9 100644 --- a/scripts/openclaw-npm-postpublish-verify.ts +++ b/scripts/openclaw-npm-postpublish-verify.ts @@ -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 { + const extensionsDir = join(packageRoot, "dist", "extensions"); + if (!existsSync(extensionsDir)) { + return new Set(); + } + const ids = new Set(); + 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, diff --git a/test/openclaw-npm-postpublish-verify.test.ts b/test/openclaw-npm-postpublish-verify.test.ts index 54c9d8bd73e..a298ec24a6e 100644 --- a/test/openclaw-npm-postpublish-verify.test.ts +++ b/test/openclaw-npm-postpublish-verify.test.ts @@ -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 });