





















@@ -997,4 +997,272 @@ describe("syncPluginsForUpdateChannel", () => {
997997}
998998}
999999});
1000+1001+it("installs an externalized bundled plugin and rewrites its old bundled path ledger", async () => {
1002+resolveBundledPluginSourcesMock.mockReturnValue(new Map());
1003+installPluginFromNpmSpecMock.mockResolvedValue(
1004+createSuccessfulNpmUpdateResult({
1005+pluginId: "legacy-chat",
1006+targetDir: "/tmp/openclaw-plugins/legacy-chat",
1007+version: "2.0.0",
1008+npmResolution: {
1009+name: "@openclaw/legacy-chat",
1010+version: "2.0.0",
1011+resolvedSpec: "@openclaw/legacy-chat@2.0.0",
1012+},
1013+}),
1014+);
1015+1016+const result = await syncPluginsForUpdateChannel({
1017+channel: "stable",
1018+externalizedBundledPluginBridges: [
1019+{
1020+bundledPluginId: "legacy-chat",
1021+npmSpec: "@openclaw/legacy-chat",
1022+channelIds: ["legacy-chat"],
1023+},
1024+],
1025+config: {
1026+channels: {
1027+"legacy-chat": {
1028+enabled: true,
1029+},
1030+},
1031+plugins: {
1032+load: { paths: [appBundledPluginRoot("legacy-chat")] },
1033+installs: {
1034+"legacy-chat": {
1035+source: "path",
1036+sourcePath: appBundledPluginRoot("legacy-chat"),
1037+installPath: appBundledPluginRoot("legacy-chat"),
1038+},
1039+},
1040+},
1041+},
1042+});
1043+1044+expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
1045+expect.objectContaining({
1046+spec: "@openclaw/legacy-chat",
1047+mode: "update",
1048+expectedPluginId: "legacy-chat",
1049+}),
1050+);
1051+expect(result.changed).toBe(true);
1052+expect(result.summary.switchedToNpm).toEqual(["legacy-chat"]);
1053+expect(result.summary.errors).toEqual([]);
1054+expect(result.config.plugins?.load?.paths).toEqual([]);
1055+expect(result.config.plugins?.installs?.["legacy-chat"]).toMatchObject({
1056+source: "npm",
1057+spec: "@openclaw/legacy-chat",
1058+installPath: "/tmp/openclaw-plugins/legacy-chat",
1059+version: "2.0.0",
1060+resolvedName: "@openclaw/legacy-chat",
1061+resolvedVersion: "2.0.0",
1062+resolvedSpec: "@openclaw/legacy-chat@2.0.0",
1063+});
1064+});
1065+1066+it("does not externalize disabled bundled plugins", async () => {
1067+resolveBundledPluginSourcesMock.mockReturnValue(new Map());
1068+1069+const result = await syncPluginsForUpdateChannel({
1070+channel: "stable",
1071+externalizedBundledPluginBridges: [
1072+{
1073+bundledPluginId: "legacy-chat",
1074+npmSpec: "@openclaw/legacy-chat",
1075+channelIds: ["legacy-chat"],
1076+},
1077+],
1078+config: {
1079+plugins: {
1080+entries: {
1081+"legacy-chat": {
1082+enabled: false,
1083+},
1084+},
1085+load: { paths: [appBundledPluginRoot("legacy-chat")] },
1086+installs: {
1087+"legacy-chat": {
1088+source: "path",
1089+sourcePath: appBundledPluginRoot("legacy-chat"),
1090+installPath: appBundledPluginRoot("legacy-chat"),
1091+},
1092+},
1093+},
1094+},
1095+});
1096+1097+expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
1098+expect(result.changed).toBe(false);
1099+expect(result.config.plugins?.installs?.["legacy-chat"]).toMatchObject({
1100+source: "path",
1101+});
1102+});
1103+1104+it("leaves config unchanged when externalized plugin installation fails", async () => {
1105+resolveBundledPluginSourcesMock.mockReturnValue(new Map());
1106+installPluginFromNpmSpecMock.mockResolvedValue({
1107+ok: false,
1108+error: "package unavailable",
1109+});
1110+const config: OpenClawConfig = {
1111+channels: {
1112+"legacy-chat": {
1113+enabled: true,
1114+},
1115+},
1116+plugins: {
1117+load: { paths: [appBundledPluginRoot("legacy-chat")] },
1118+installs: {
1119+"legacy-chat": {
1120+source: "path",
1121+sourcePath: appBundledPluginRoot("legacy-chat"),
1122+installPath: appBundledPluginRoot("legacy-chat"),
1123+},
1124+},
1125+},
1126+};
1127+1128+const result = await syncPluginsForUpdateChannel({
1129+channel: "stable",
1130+externalizedBundledPluginBridges: [
1131+{
1132+bundledPluginId: "legacy-chat",
1133+npmSpec: "@openclaw/legacy-chat",
1134+channelIds: ["legacy-chat"],
1135+},
1136+],
1137+ config,
1138+});
1139+1140+expect(result.changed).toBe(false);
1141+expect(result.config).toBe(config);
1142+expect(result.summary.errors).toEqual(["Failed to update legacy-chat: package unavailable"]);
1143+});
1144+1145+it("does not externalize custom local path installs that only share the old plugin id", async () => {
1146+resolveBundledPluginSourcesMock.mockReturnValue(new Map());
1147+1148+const result = await syncPluginsForUpdateChannel({
1149+channel: "stable",
1150+externalizedBundledPluginBridges: [
1151+{
1152+bundledPluginId: "legacy-chat",
1153+npmSpec: "@openclaw/legacy-chat",
1154+channelIds: ["legacy-chat"],
1155+},
1156+],
1157+config: {
1158+channels: {
1159+"legacy-chat": {
1160+enabled: true,
1161+},
1162+},
1163+plugins: {
1164+load: { paths: ["/workspace/plugins/legacy-chat"] },
1165+installs: {
1166+"legacy-chat": {
1167+source: "path",
1168+sourcePath: "/workspace/plugins/legacy-chat",
1169+installPath: "/workspace/plugins/legacy-chat",
1170+},
1171+},
1172+},
1173+},
1174+});
1175+1176+expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
1177+expect(result.changed).toBe(false);
1178+expect(result.config.plugins?.installs?.["legacy-chat"]).toMatchObject({
1179+source: "path",
1180+sourcePath: "/workspace/plugins/legacy-chat",
1181+});
1182+});
1183+1184+it("does not externalize while the bundled source is still present in the current build", async () => {
1185+mockBundledSources(
1186+createBundledSource({
1187+pluginId: "legacy-chat",
1188+localPath: appBundledPluginRoot("legacy-chat"),
1189+}),
1190+);
1191+1192+const result = await syncPluginsForUpdateChannel({
1193+channel: "stable",
1194+externalizedBundledPluginBridges: [
1195+{
1196+bundledPluginId: "legacy-chat",
1197+npmSpec: "@openclaw/legacy-chat",
1198+channelIds: ["legacy-chat"],
1199+},
1200+],
1201+config: {
1202+channels: {
1203+"legacy-chat": {
1204+enabled: true,
1205+},
1206+},
1207+plugins: {
1208+load: { paths: [appBundledPluginRoot("legacy-chat")] },
1209+installs: {
1210+"legacy-chat": {
1211+source: "path",
1212+sourcePath: appBundledPluginRoot("legacy-chat"),
1213+installPath: appBundledPluginRoot("legacy-chat"),
1214+},
1215+},
1216+},
1217+},
1218+});
1219+1220+expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
1221+expect(result.changed).toBe(false);
1222+expect(result.config.plugins?.installs?.["legacy-chat"]).toMatchObject({
1223+source: "path",
1224+});
1225+});
1226+1227+it("removes stale bundled load paths for already-externalized npm installs", async () => {
1228+resolveBundledPluginSourcesMock.mockReturnValue(new Map());
1229+1230+const result = await syncPluginsForUpdateChannel({
1231+channel: "stable",
1232+externalizedBundledPluginBridges: [
1233+{
1234+bundledPluginId: "legacy-chat",
1235+npmSpec: "@openclaw/legacy-chat",
1236+channelIds: ["legacy-chat"],
1237+},
1238+],
1239+config: {
1240+channels: {
1241+"legacy-chat": {
1242+enabled: true,
1243+},
1244+},
1245+plugins: {
1246+load: {
1247+paths: [appBundledPluginRoot("legacy-chat"), "/workspace/plugins/other"],
1248+},
1249+installs: {
1250+"legacy-chat": {
1251+source: "npm",
1252+spec: "@openclaw/legacy-chat",
1253+installPath: "/tmp/openclaw-plugins/legacy-chat",
1254+},
1255+},
1256+},
1257+},
1258+});
1259+1260+expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
1261+expect(result.changed).toBe(true);
1262+expect(result.config.plugins?.load?.paths).toEqual(["/workspace/plugins/other"]);
1263+expect(result.config.plugins?.installs?.["legacy-chat"]).toMatchObject({
1264+source: "npm",
1265+spec: "@openclaw/legacy-chat",
1266+});
1267+});
10001268});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。