Build: enforce mirrored Matrix dep version parity

This commit is contained in:
Gustavo Madeira Santana
2026-03-29 15:31:53 -04:00
parent c6f461563c
commit da76c94b3a
3 changed files with 97 additions and 20 deletions

View File

@@ -60,6 +60,16 @@ function collectBundledExtensions(): BundledExtension[] {
});
}
function collectRuntimeDependencySpecs(packageJson: {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
}): Map<string, string> {
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<string, string>;
optionalDependencies?: Record<string, string>;
};
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<string>,
rootRuntimeDeps: ReadonlyMap<string, string>,
): 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}')`,
);
}
}
}

View File

@@ -59,6 +59,36 @@ function readRootPackageJson(): {
};
}
function readMatrixPackageJson(): {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
openclaw?: {
releaseChecks?: {
rootDependencyMirrorAllowlist?: unknown;
};
};
} {
return JSON.parse(readFileSync(resolve(REPO_ROOT, "extensions/matrix/package.json"), "utf8")) as {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
openclaw?: {
releaseChecks?: {
rootDependencyMirrorAllowlist?: unknown;
};
};
};
}
function collectRuntimeDependencySpecs(packageJson: {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
}): Map<string, string> {
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<string, string>;
};
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",

View File

@@ -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([]);
});