From bbc3849e244990c3caacb72ed9d23bf138d79fad Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 11 Apr 2026 16:46:25 +0100 Subject: [PATCH] test: allow npm qa compat sidecars --- scripts/openclaw-npm-postpublish-verify.ts | 25 ++++++++++++++++++ test/openclaw-npm-postpublish-verify.test.ts | 27 ++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/scripts/openclaw-npm-postpublish-verify.ts b/scripts/openclaw-npm-postpublish-verify.ts index b5b3b48656c..7fd04e41297 100644 --- a/scripts/openclaw-npm-postpublish-verify.ts +++ b/scripts/openclaw-npm-postpublish-verify.ts @@ -20,6 +20,7 @@ import { collectRootDistBundledRuntimeMirrors, collectRuntimeDependencySpecs, } from "./lib/bundled-plugin-root-runtime-mirrors.mjs"; +import { NPM_UPDATE_COMPAT_SIDECAR_PATHS } from "./lib/npm-update-compat-sidecars.mjs"; import { parseReleaseVersion, resolveNpmCommandInvocation } from "./openclaw-npm-release-check.ts"; type InstalledPackageJson = { @@ -40,6 +41,13 @@ type InstalledBundledExtensionManifestRecord = { }; const MAX_BUNDLED_EXTENSION_MANIFEST_BYTES = 1024 * 1024; +const NPM_UPDATE_COMPAT_EXTENSION_DIRS = new Set( + [...NPM_UPDATE_COMPAT_SIDECAR_PATHS].map((relativePath) => { + const pathParts = relativePath.split("/"); + pathParts.pop(); + return pathParts.join("/"); + }), +); export type PublishedInstallScenario = { name: string; @@ -129,6 +137,20 @@ function collectExpectedBundledExtensionPackageIds( return ids; } +function isNpmUpdateCompatOnlyExtensionDir(params: { + extensionId: string; + packageRoot: string; +}): boolean { + const relativeExtensionDir = `dist/extensions/${params.extensionId}`; + if (!NPM_UPDATE_COMPAT_EXTENSION_DIRS.has(relativeExtensionDir)) { + return false; + } + + return [...NPM_UPDATE_COMPAT_SIDECAR_PATHS] + .filter((relativePath) => relativePath.startsWith(`${relativeExtensionDir}/`)) + .every((relativePath) => existsSync(join(params.packageRoot, relativePath))); +} + function readBundledExtensionPackageJsons(packageRoot: string): { manifests: InstalledBundledExtensionManifestRecord[]; errors: string[]; @@ -150,6 +172,9 @@ function readBundledExtensionPackageJsons(packageRoot: string): { const extensionDirPath = join(extensionsDir, entry.name); const packageJsonPath = join(extensionsDir, entry.name, "package.json"); if (!existsSync(packageJsonPath)) { + if (isNpmUpdateCompatOnlyExtensionDir({ extensionId: entry.name, packageRoot })) { + continue; + } if (expectedPackageIds === null || expectedPackageIds.has(entry.name)) { errors.push(`installed bundled extension manifest missing: ${packageJsonPath}.`); } diff --git a/test/openclaw-npm-postpublish-verify.test.ts b/test/openclaw-npm-postpublish-verify.test.ts index 96b4afee732..61faa26eeb4 100644 --- a/test/openclaw-npm-postpublish-verify.test.ts +++ b/test/openclaw-npm-postpublish-verify.test.ts @@ -258,6 +258,33 @@ describe("collectInstalledMirroredRootDependencyManifestErrors", () => { } }); + it("allows npm update compatibility sidecar directories without package.json", () => { + const packageRoot = makeInstalledPackageRoot(); + + try { + writePackageFile(packageRoot, "package.json", { + version: "2026.4.10", + dependencies: {}, + }); + mkdirSync(join(packageRoot, "dist/extensions/qa-channel"), { recursive: true }); + mkdirSync(join(packageRoot, "dist/extensions/qa-lab"), { recursive: true }); + writeFileSync( + join(packageRoot, "dist/extensions/qa-channel/runtime-api.js"), + "export {};\n", + "utf8", + ); + writeFileSync( + join(packageRoot, "dist/extensions/qa-lab/runtime-api.js"), + "export {};\n", + "utf8", + ); + + expect(collectInstalledMirroredRootDependencyManifestErrors(packageRoot)).toEqual([]); + } finally { + rmSync(packageRoot, { recursive: true, force: true }); + } + }); + it("rejects bundled extension manifests that are not regular files", () => { const packageRoot = makeInstalledPackageRoot(); const outsideManifestRoot = makeInstalledPackageRoot();