mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-19 04:28:22 +00:00
fix(matrix): stop runtime npm install from parent-derived cwd
`ensureMatrixSdkInstalled` previously derived an install `cwd` via fixed two-segment traversal from `import.meta.url` and spawned `npm install` (or `pnpm install`) when Matrix packages were missing. Under the externalized plugin layout the derived path is a scope directory like `<config>/npm/node_modules/@openclaw`, so npm walks up to the managed project root and prunes undeclared siblings. Under the legacy bundled layout it would target `<global-prefix>/lib/node_modules` and could delete unrelated global CLIs. Matrix is now a pure availability check: if any required package fails to resolve, it throws an actionable error pointing the operator at the supported repair commands (`openclaw plugins update matrix`, `openclaw doctor --fix`). This matches extensions/AGENTS.md: "Runtime never installs deps; install/update/doctor are repair points." The exported signature stays backwards-compatible (all params optional; `confirm` and `runtime` are accepted but ignored). `resolveMissingMatrixPackages` gains an optional `resolveFn` seam for testability, mirroring the existing `ensureMatrixCryptoRuntime` injection pattern. Fixes #80758.
This commit is contained in:
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { ensureMatrixCryptoRuntime } from "./deps.js";
|
||||
import { ensureMatrixCryptoRuntime, ensureMatrixSdkInstalled } from "./deps.js";
|
||||
|
||||
const logStub = vi.fn();
|
||||
|
||||
@@ -160,3 +160,47 @@ describe("ensureMatrixCryptoRuntime", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ensureMatrixSdkInstalled", () => {
|
||||
it("returns without error when all required packages resolve", async () => {
|
||||
const resolveFn = vi.fn((_id: string) => "/fake/path");
|
||||
await expect(ensureMatrixSdkInstalled({ resolveFn })).resolves.toBeUndefined();
|
||||
expect(resolveFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws actionable repair error listing every missing package", async () => {
|
||||
const resolveFn = vi.fn((_id: string) => {
|
||||
throw new Error("Cannot find module");
|
||||
});
|
||||
await expect(ensureMatrixSdkInstalled({ resolveFn })).rejects.toThrow(
|
||||
/matrix-js-sdk.*@matrix-org\/matrix-sdk-crypto-nodejs.*@matrix-org\/matrix-sdk-crypto-wasm/s,
|
||||
);
|
||||
await expect(ensureMatrixSdkInstalled({ resolveFn })).rejects.toThrow(
|
||||
/openclaw plugins update matrix/,
|
||||
);
|
||||
await expect(ensureMatrixSdkInstalled({ resolveFn })).rejects.toThrow(/openclaw doctor --fix/);
|
||||
});
|
||||
|
||||
it("lists only the packages that fail to resolve", async () => {
|
||||
const resolveFn = vi.fn((id: string) => {
|
||||
if (id === "@matrix-org/matrix-sdk-crypto-wasm") {
|
||||
throw new Error("Cannot find module");
|
||||
}
|
||||
return "/fake/path";
|
||||
});
|
||||
await expect(ensureMatrixSdkInstalled({ resolveFn })).rejects.toThrow(
|
||||
/Matrix plugin dependencies are missing: @matrix-org\/matrix-sdk-crypto-wasm\./,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not invoke the install confirm prompt when packages are missing (regression: #80758)", async () => {
|
||||
const confirm = vi.fn(async () => true);
|
||||
const resolveFn = vi.fn((_id: string) => {
|
||||
throw new Error("Cannot find module");
|
||||
});
|
||||
await expect(ensureMatrixSdkInstalled({ resolveFn, confirm })).rejects.toThrow(
|
||||
/Matrix plugin dependencies are missing/,
|
||||
);
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { spawn } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
||||
|
||||
@@ -26,12 +25,12 @@ type MatrixCryptoRuntimeDeps = {
|
||||
log?: (message: string) => void;
|
||||
};
|
||||
|
||||
function resolveMissingMatrixPackages(): string[] {
|
||||
function resolveMissingMatrixPackages(resolveFn?: (id: string) => string): string[] {
|
||||
try {
|
||||
const req = createRequire(import.meta.url);
|
||||
const resolve = resolveFn ?? defaultResolveFn;
|
||||
return REQUIRED_MATRIX_PACKAGES.filter((pkg) => {
|
||||
try {
|
||||
req.resolve(pkg);
|
||||
resolve(pkg);
|
||||
return false;
|
||||
} catch {
|
||||
return true;
|
||||
@@ -46,9 +45,12 @@ export function isMatrixSdkAvailable(): boolean {
|
||||
return resolveMissingMatrixPackages().length === 0;
|
||||
}
|
||||
|
||||
function resolvePluginRoot(): string {
|
||||
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
return path.resolve(currentDir, "..", "..");
|
||||
function buildMatrixDepsMissingMessage(missing: string[]): string {
|
||||
const packages = missing.length > 0 ? missing.join(", ") : REQUIRED_MATRIX_PACKAGES.join(", ");
|
||||
return [
|
||||
`Matrix plugin dependencies are missing: ${packages}.`,
|
||||
"Repair this plugin with `openclaw plugins update matrix` or run `openclaw doctor --fix`.",
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
type CommandResult = {
|
||||
@@ -299,47 +301,14 @@ async function ensureMatrixCryptoRuntimeOnce(params: MatrixCryptoRuntimeDeps): P
|
||||
requireFn("@matrix-org/matrix-sdk-crypto-nodejs");
|
||||
}
|
||||
|
||||
export async function ensureMatrixSdkInstalled(params: {
|
||||
runtime: RuntimeEnv;
|
||||
export async function ensureMatrixSdkInstalled(params?: {
|
||||
runtime?: RuntimeEnv;
|
||||
confirm?: (message: string) => Promise<boolean>;
|
||||
resolveFn?: (id: string) => string;
|
||||
}): Promise<void> {
|
||||
if (isMatrixSdkAvailable()) {
|
||||
const missing = resolveMissingMatrixPackages(params?.resolveFn);
|
||||
if (missing.length === 0) {
|
||||
return;
|
||||
}
|
||||
const confirm = params.confirm;
|
||||
if (confirm) {
|
||||
const ok = await confirm(
|
||||
"Matrix requires matrix-js-sdk, @matrix-org/matrix-sdk-crypto-nodejs, and @matrix-org/matrix-sdk-crypto-wasm. Install now?",
|
||||
);
|
||||
if (!ok) {
|
||||
throw new Error(
|
||||
"Matrix requires matrix-js-sdk, @matrix-org/matrix-sdk-crypto-nodejs, and @matrix-org/matrix-sdk-crypto-wasm (install dependencies first).",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const root = resolvePluginRoot();
|
||||
const command = fs.existsSync(path.join(root, "pnpm-lock.yaml"))
|
||||
? ["pnpm", "install"]
|
||||
: ["npm", "install", "--omit=dev", "--silent"];
|
||||
params.runtime.log?.(`matrix: installing dependencies via ${command[0]} (${root})…`);
|
||||
const result = await runFixedCommandWithTimeout({
|
||||
argv: command,
|
||||
cwd: root,
|
||||
timeoutMs: 300_000,
|
||||
env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
|
||||
});
|
||||
if (result.code !== 0) {
|
||||
throw new Error(
|
||||
result.stderr.trim() || result.stdout.trim() || "Matrix dependency install failed.",
|
||||
);
|
||||
}
|
||||
if (!isMatrixSdkAvailable()) {
|
||||
const missing = resolveMissingMatrixPackages();
|
||||
throw new Error(
|
||||
missing.length > 0
|
||||
? `Matrix dependency install completed but required packages are still missing: ${missing.join(", ")}`
|
||||
: "Matrix dependency install completed but Matrix dependencies are still missing.",
|
||||
);
|
||||
}
|
||||
throw new Error(buildMatrixDepsMissingMessage(missing));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user