


























@@ -1294,6 +1294,141 @@ describe("installPluginFromNpmSpec", () => {
12941294).toBe(true);
12951295});
129612961297+it("resolves incompatible prerelease tags to a compatible prerelease version", async () => {
1298+const stateDir = suiteTempRootTracker.makeTempDir();
1299+const npmRoot = path.join(stateDir, "npm");
1300+const warnings: string[] = [];
1301+vi.stubEnv("OPENCLAW_COMPATIBILITY_HOST_VERSION", "2026.5.28-beta.3");
1302+1303+mockNpmViewAndInstallMany([
1304+{
1305+spec: "@openclaw/msteams@beta",
1306+packageName: "@openclaw/msteams",
1307+version: "2026.5.28-beta.4",
1308+pluginId: "msteams",
1309+ npmRoot,
1310+versions: ["2026.5.28-beta.3", "2026.5.28-beta.4"],
1311+openclaw: {
1312+extensions: ["./dist/index.js"],
1313+compat: { pluginApi: ">=2026.5.28-beta.4" },
1314+},
1315+},
1316+{
1317+spec: "@openclaw/msteams@2026.5.28-beta.3",
1318+packageName: "@openclaw/msteams",
1319+version: "2026.5.28-beta.3",
1320+pluginId: "msteams",
1321+ npmRoot,
1322+expectedDependencySpec: "2026.5.28-beta.3",
1323+openclaw: {
1324+extensions: ["./dist/index.js"],
1325+compat: { pluginApi: ">=2026.5.28-beta.3" },
1326+},
1327+},
1328+]);
1329+1330+const result = await installPluginFromNpmSpec({
1331+spec: "@openclaw/msteams@beta",
1332+npmDir: npmRoot,
1333+mode: "update",
1334+logger: { info: () => {}, warn: (message) => warnings.push(message) },
1335+});
1336+1337+expect(result.ok).toBe(true);
1338+if (!result.ok) {
1339+return;
1340+}
1341+expect(result.npmResolution?.resolvedSpec).toBe("@openclaw/msteams@2026.5.28-beta.3");
1342+expect(result.npmResolution?.version).toBe("2026.5.28-beta.3");
1343+expect(warnings.join("\n")).toContain(
1344+"using newest compatible @openclaw/msteams@2026.5.28-beta.3",
1345+);
1346+const npmProjectRoot = resolvePluginNpmProjectDir({
1347+npmDir: npmRoot,
1348+packageName: "@openclaw/msteams",
1349+});
1350+const managedManifest = JSON.parse(
1351+fs.readFileSync(path.join(npmProjectRoot, "package.json"), "utf8"),
1352+) as { dependencies?: Record<string, string> };
1353+expect(managedManifest.dependencies?.["@openclaw/msteams"]).toBe("2026.5.28-beta.3");
1354+});
1355+1356+it("does not resolve explicit prerelease tags to stable compatible versions", async () => {
1357+const stateDir = suiteTempRootTracker.makeTempDir();
1358+const npmRoot = path.join(stateDir, "npm");
1359+vi.stubEnv("OPENCLAW_COMPATIBILITY_HOST_VERSION", "2026.5.28-beta.3");
1360+1361+mockNpmViewAndInstallMany([
1362+{
1363+spec: "@openclaw/msteams@beta",
1364+packageName: "@openclaw/msteams",
1365+version: "2026.5.28-beta.4",
1366+pluginId: "msteams",
1367+ npmRoot,
1368+versions: ["2026.5.27", "2026.5.28-beta.4"],
1369+openclaw: {
1370+extensions: ["./dist/index.js"],
1371+compat: { pluginApi: ">=2026.5.28-beta.4" },
1372+},
1373+},
1374+]);
1375+1376+const result = await installPluginFromNpmSpec({
1377+spec: "@openclaw/msteams@beta",
1378+npmDir: npmRoot,
1379+mode: "update",
1380+logger: { info: () => {}, warn: () => {} },
1381+});
1382+1383+expect(result.ok).toBe(false);
1384+if (result.ok) {
1385+return;
1386+}
1387+expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.INCOMPATIBLE_PLUGIN_API);
1388+expect(result.error).toContain("requires plugin API >=2026.5.28-beta.4");
1389+expect(
1390+runCommandWithTimeoutMock.mock.calls.some(([argv]) => isManagedNpmInstallCommand(argv)),
1391+).toBe(false);
1392+});
1393+1394+it("does not resolve explicit prerelease tags to a different prerelease channel", async () => {
1395+const stateDir = suiteTempRootTracker.makeTempDir();
1396+const npmRoot = path.join(stateDir, "npm");
1397+vi.stubEnv("OPENCLAW_COMPATIBILITY_HOST_VERSION", "2026.5.28-beta.3");
1398+1399+mockNpmViewAndInstallMany([
1400+{
1401+spec: "@openclaw/msteams@beta",
1402+packageName: "@openclaw/msteams",
1403+version: "2026.5.28-beta.4",
1404+pluginId: "msteams",
1405+ npmRoot,
1406+versions: ["2026.5.28-alpha.10", "2026.5.28-beta.4"],
1407+openclaw: {
1408+extensions: ["./dist/index.js"],
1409+compat: { pluginApi: ">=2026.5.28-beta.4" },
1410+},
1411+},
1412+]);
1413+1414+const result = await installPluginFromNpmSpec({
1415+spec: "@openclaw/msteams@beta",
1416+npmDir: npmRoot,
1417+mode: "update",
1418+logger: { info: () => {}, warn: () => {} },
1419+});
1420+1421+expect(result.ok).toBe(false);
1422+if (result.ok) {
1423+return;
1424+}
1425+expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.INCOMPATIBLE_PLUGIN_API);
1426+expect(result.error).toContain("requires plugin API >=2026.5.28-beta.4");
1427+expect(
1428+runCommandWithTimeoutMock.mock.calls.some(([argv]) => isManagedNpmInstallCommand(argv)),
1429+).toBe(false);
1430+});
1431+12971432it.runIf(process.platform !== "win32")(
12981433"repairs root openclaw materialized by npm peer handling",
12991434async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。