From da76c94b3a03033db8aae20263f953cd249536b4 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Sun, 29 Mar 2026 15:31:53 -0400 Subject: [PATCH] Build: enforce mirrored Matrix dep version parity --- scripts/release-check.ts | 34 +++++++++---- .../package-contract-guardrails.test.ts | 50 +++++++++++++++++-- test/release-check.test.ts | 33 ++++++++++-- 3 files changed, 97 insertions(+), 20 deletions(-) diff --git a/scripts/release-check.ts b/scripts/release-check.ts index 8705e9f6743..135e2613274 100755 --- a/scripts/release-check.ts +++ b/scripts/release-check.ts @@ -60,6 +60,16 @@ function collectBundledExtensions(): BundledExtension[] { }); } +function collectRuntimeDependencySpecs(packageJson: { + dependencies?: Record; + optionalDependencies?: Record; +}): Map { + return new Map([ + ...Object.entries(packageJson.dependencies ?? {}), + ...Object.entries(packageJson.optionalDependencies ?? {}), + ]); +} + function checkBundledExtensionMetadata() { const extensions = collectBundledExtensions(); const manifestErrors = collectBundledExtensionManifestErrors(extensions); @@ -67,10 +77,7 @@ function checkBundledExtensionMetadata() { dependencies?: Record; optionalDependencies?: Record; }; - const rootRuntimeDeps = new Set([ - ...Object.keys(rootPackage.dependencies ?? {}), - ...Object.keys(rootPackage.optionalDependencies ?? {}), - ]); + const rootRuntimeDeps = collectRuntimeDependencySpecs(rootPackage); const rootMirrorErrors = collectBundledExtensionRootDependencyMirrorErrors( extensions, rootRuntimeDeps, @@ -87,7 +94,7 @@ function checkBundledExtensionMetadata() { export function collectBundledExtensionRootDependencyMirrorErrors( extensions: BundledExtension[], - rootRuntimeDeps: ReadonlySet, + rootRuntimeDeps: ReadonlyMap, ): string[] { const errors: string[] = []; @@ -106,10 +113,7 @@ export function collectBundledExtensionRootDependencyMirrorErrors( continue; } - const extensionRuntimeDeps = new Set([ - ...Object.keys(extension.packageJson.dependencies ?? {}), - ...Object.keys(extension.packageJson.optionalDependencies ?? {}), - ]); + const extensionRuntimeDeps = collectRuntimeDependencySpecs(extension.packageJson); for (const entry of allowlist) { if (typeof entry !== "string" || entry.trim().length === 0) { @@ -119,15 +123,23 @@ export function collectBundledExtensionRootDependencyMirrorErrors( continue; } - if (!extensionRuntimeDeps.has(entry)) { + const extensionSpec = extensionRuntimeDeps.get(entry); + if (!extensionSpec) { errors.push( `bundled extension '${extension.id}' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '${entry}' must be declared in extension runtime dependencies`, ); } - if (!rootRuntimeDeps.has(entry)) { + const rootSpec = rootRuntimeDeps.get(entry); + if (!rootSpec) { errors.push( `bundled extension '${extension.id}' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '${entry}' must be mirrored in root runtime dependencies`, ); + continue; + } + if (extensionSpec !== rootSpec) { + errors.push( + `bundled extension '${extension.id}' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '${entry}' must match root runtime dependency version (extension '${extensionSpec}', root '${rootSpec}')`, + ); } } } diff --git a/src/plugin-sdk/package-contract-guardrails.test.ts b/src/plugin-sdk/package-contract-guardrails.test.ts index fc2e8cf27c6..05dd5fdb0ca 100644 --- a/src/plugin-sdk/package-contract-guardrails.test.ts +++ b/src/plugin-sdk/package-contract-guardrails.test.ts @@ -59,6 +59,36 @@ function readRootPackageJson(): { }; } +function readMatrixPackageJson(): { + dependencies?: Record; + optionalDependencies?: Record; + openclaw?: { + releaseChecks?: { + rootDependencyMirrorAllowlist?: unknown; + }; + }; +} { + return JSON.parse(readFileSync(resolve(REPO_ROOT, "extensions/matrix/package.json"), "utf8")) as { + dependencies?: Record; + optionalDependencies?: Record; + openclaw?: { + releaseChecks?: { + rootDependencyMirrorAllowlist?: unknown; + }; + }; + }; +} + +function collectRuntimeDependencySpecs(packageJson: { + dependencies?: Record; + optionalDependencies?: Record; +}): Map { + return new Map([ + ...Object.entries(packageJson.dependencies ?? {}), + ...Object.entries(packageJson.optionalDependencies ?? {}), + ]); +} + function createRootPackageRequire() { return createRequire(pathToFileURL(resolve(REPO_ROOT, "package.json")).href); } @@ -126,11 +156,20 @@ describe("plugin-sdk package contract guardrails", () => { }); it("mirrors matrix runtime deps needed by the bundled host graph", () => { - const { dependencies = {}, optionalDependencies = {} } = readRootPackageJson(); + const rootRuntimeDeps = collectRuntimeDependencySpecs(readRootPackageJson()); + const matrixPackageJson = readMatrixPackageJson(); + const matrixRuntimeDeps = collectRuntimeDependencySpecs(matrixPackageJson); + const allowlist = matrixPackageJson.openclaw?.releaseChecks?.rootDependencyMirrorAllowlist; - expect(dependencies["@matrix-org/matrix-sdk-crypto-wasm"]).toBe("18.0.0"); - expect(dependencies["matrix-js-sdk"]).toBe("41.2.0"); - expect(optionalDependencies["@matrix-org/matrix-sdk-crypto-nodejs"]).toBe("^0.4.0"); + expect(Array.isArray(allowlist)).toBe(true); + const matrixRootMirrorAllowlist = allowlist as string[]; + expect(matrixRootMirrorAllowlist).toEqual( + expect.arrayContaining(["@matrix-org/matrix-sdk-crypto-wasm"]), + ); + + for (const dep of matrixRootMirrorAllowlist) { + expect(rootRuntimeDeps.get(dep)).toBe(matrixRuntimeDeps.get(dep)); + } }); it("resolves matrix crypto WASM from the root runtime surface", () => { @@ -177,9 +216,10 @@ describe("plugin-sdk package contract guardrails", () => { dependencies?: Record; }; const installedRequire = createRequire(pathToFileURL(installedPackageJsonPath).href); + const matrixPackageJson = readMatrixPackageJson(); expect(installedPackageJson.dependencies?.["@matrix-org/matrix-sdk-crypto-wasm"]).toBe( - "18.0.0", + matrixPackageJson.dependencies?.["@matrix-org/matrix-sdk-crypto-wasm"], ); expect(installedRequire.resolve("@matrix-org/matrix-sdk-crypto-wasm")).toContain( "@matrix-org/matrix-sdk-crypto-wasm", diff --git a/test/release-check.test.ts b/test/release-check.test.ts index 88098962f21..98034974e54 100644 --- a/test/release-check.test.ts +++ b/test/release-check.test.ts @@ -126,7 +126,7 @@ describe("collectBundledExtensionRootDependencyMirrorErrors", () => { }, }, ], - new Set(), + new Map(), ), ).toEqual([ "bundled extension 'matrix' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist must be an array", @@ -151,7 +151,7 @@ describe("collectBundledExtensionRootDependencyMirrorErrors", () => { }, }, ], - new Set(["@matrix-org/matrix-sdk-crypto-wasm"]), + new Map([["@matrix-org/matrix-sdk-crypto-wasm", "18.0.0"]]), ), ).toEqual([ "bundled extension 'matrix' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '@matrix-org/matrix-sdk-crypto-wasm' must be declared in extension runtime dependencies", @@ -176,13 +176,38 @@ describe("collectBundledExtensionRootDependencyMirrorErrors", () => { }, }, ], - new Set(), + new Map(), ), ).toEqual([ "bundled extension 'matrix' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '@matrix-org/matrix-sdk-crypto-wasm' must be mirrored in root runtime dependencies", ]); }); + it("flags mirror entries whose root version drifts from the extension", () => { + expect( + collectBundledExtensionRootDependencyMirrorErrors( + [ + { + id: "matrix", + packageJson: { + dependencies: { + "@matrix-org/matrix-sdk-crypto-wasm": "18.0.0", + }, + openclaw: { + releaseChecks: { + rootDependencyMirrorAllowlist: ["@matrix-org/matrix-sdk-crypto-wasm"], + }, + }, + }, + }, + ], + new Map([["@matrix-org/matrix-sdk-crypto-wasm", "18.1.0"]]), + ), + ).toEqual([ + "bundled extension 'matrix' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '@matrix-org/matrix-sdk-crypto-wasm' must match root runtime dependency version (extension '18.0.0', root '18.1.0')", + ]); + }); + it("accepts mirror entries declared by both the extension and root package", () => { expect( collectBundledExtensionRootDependencyMirrorErrors( @@ -201,7 +226,7 @@ describe("collectBundledExtensionRootDependencyMirrorErrors", () => { }, }, ], - new Set(["@matrix-org/matrix-sdk-crypto-wasm"]), + new Map([["@matrix-org/matrix-sdk-crypto-wasm", "18.0.0"]]), ), ).toEqual([]); });