dsh-plugin-discovery 1.2.0 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +104 -96
  2. package/client.js +646 -24
  3. package/data/recommended.json +1206 -1206
  4. package/index.js +318 -14
  5. package/package.json +74 -74
package/client.js CHANGED
@@ -49,6 +49,27 @@ window.__ModuleLoader__.load({
49
49
  installLog: "安装输出:{text}",
50
50
  installFailed: "安装失败",
51
51
  retry: "重试",
52
+ copy: "复制",
53
+ copied: "已复制",
54
+ copyFailed: "复制失败",
55
+ riskInstall: "承担风险安装",
56
+ riskTitle: "版本不兼容,确认风险后安装?",
57
+ riskDesc:
58
+ "插件 {package} 与当前 DSH {runtime} 声明的 peer 不匹配。强行安装可能导致崩溃或数据丢失。仅在你明确愿意承担该风险时继续。",
59
+ riskAck: "我已了解风险,仍要为这对精确版本授予豁免并重试安装",
60
+ riskCancel: "取消",
61
+ riskConfirm: "确认豁免并安装",
62
+ riskWorking: "正在授权并重试…",
63
+ riskAltTitle: "推荐改用兼容插件",
64
+ riskAltDesc: "以下精选包更可能匹配当前 DSH 版本,可直接安装代替强行豁免:",
65
+ riskAltInstall: "安装推荐",
66
+ buildTitle: "需要批准依赖构建脚本",
67
+ buildDesc:
68
+ "安装「{spec}」时,pnpm 拦截了以下依赖的安装脚本(常用于编译原生模块)。批准后将以你的用户权限执行这些脚本,并重试安装。",
69
+ buildAck: "我已了解风险,允许这些依赖运行安装脚本并重试",
70
+ buildCancel: "取消",
71
+ buildConfirm: "批准并重试安装",
72
+ buildWorking: "正在批准并重试…",
52
73
  browseRecommend: "为你推荐",
53
74
  browseCategory: "分类浏览",
54
75
  browseSearch: "在线搜索",
@@ -129,6 +150,27 @@ window.__ModuleLoader__.load({
129
150
  installLog: "Output: {text}",
130
151
  installFailed: "Install failed",
131
152
  retry: "Retry",
153
+ copy: "Copy",
154
+ copied: "Copied",
155
+ copyFailed: "Copy failed",
156
+ riskInstall: "Install anyway",
157
+ riskTitle: "Incompatible version — install at your own risk?",
158
+ riskDesc:
159
+ "Plugin {package} does not match peers for DSH {runtime}. Forcing install may crash or lose data. Continue only if you accept that risk.",
160
+ riskAck: "I understand the risk and grant an exact-version exemption for this pair",
161
+ riskCancel: "Cancel",
162
+ riskConfirm: "Grant exemption and install",
163
+ riskWorking: "Granting and retrying…",
164
+ riskAltTitle: "Try a compatible plugin instead",
165
+ riskAltDesc: "These curated packages are more likely to match this DSH version:",
166
+ riskAltInstall: "Install recommended",
167
+ buildTitle: "Dependency build scripts need approval",
168
+ buildDesc:
169
+ "Installing “{spec}” was blocked because pnpm ignored build scripts for: the packages listed below (often native modules). Approving runs those scripts as your user and retries the install.",
170
+ buildAck: "I understand and allow these packages to run install scripts",
171
+ buildCancel: "Cancel",
172
+ buildConfirm: "Approve and retry",
173
+ buildWorking: "Approving and retrying…",
132
174
  browseRecommend: "For you",
133
175
  browseCategory: "Categories",
134
176
  browseSearch: "Online search",
@@ -593,7 +635,7 @@ window.__ModuleLoader__.load({
593
635
  },
594
636
  errorBox: {
595
637
  display: "flex",
596
- alignItems: "center",
638
+ alignItems: "flex-start",
597
639
  justifyContent: "space-between",
598
640
  gap: "12px",
599
641
  padding: "12px 14px",
@@ -602,6 +644,115 @@ window.__ModuleLoader__.load({
602
644
  background: "var(--dsw-alias-settings-card-fill, #ffffff)",
603
645
  color: "var(--dsw-alias-state-warn-primary, var(--dsw-alias-label-secondary))",
604
646
  fontSize: "13px",
647
+ lineHeight: "20px",
648
+ },
649
+ errorText: {
650
+ flex: 1,
651
+ minWidth: 0,
652
+ whiteSpace: "pre-wrap",
653
+ wordBreak: "break-word",
654
+ overflowWrap: "anywhere",
655
+ },
656
+ errorActions: {
657
+ display: "flex",
658
+ alignItems: "center",
659
+ gap: "8px",
660
+ flexShrink: 0,
661
+ flexWrap: "wrap",
662
+ justifyContent: "flex-end",
663
+ },
664
+ riskOverlay: {
665
+ position: "fixed",
666
+ inset: 0,
667
+ zIndex: 10000,
668
+ display: "flex",
669
+ alignItems: "center",
670
+ justifyContent: "center",
671
+ padding: "24px",
672
+ background: "rgba(15, 23, 42, 0.45)",
673
+ },
674
+ riskDialog: {
675
+ width: "min(520px, 100%)",
676
+ maxHeight: "90vh",
677
+ overflow: "auto",
678
+ borderRadius: "var(--dsw-radius-md, 12px)",
679
+ border: "1px solid var(--dsw-alias-border-l2, #d0d5dd)",
680
+ background: "var(--dsw-alias-settings-card-fill, #ffffff)",
681
+ color: "var(--dsw-alias-label-primary)",
682
+ padding: "20px",
683
+ boxShadow: "0 16px 40px rgba(15, 23, 42, 0.18)",
684
+ },
685
+ riskTitle: {
686
+ margin: "0 0 10px",
687
+ fontSize: "16px",
688
+ fontWeight: 600,
689
+ lineHeight: "24px",
690
+ },
691
+ riskDesc: {
692
+ margin: "0 0 14px",
693
+ fontSize: "13px",
694
+ lineHeight: "20px",
695
+ color: "var(--dsw-alias-label-secondary)",
696
+ whiteSpace: "pre-wrap",
697
+ },
698
+ riskAckRow: {
699
+ display: "flex",
700
+ alignItems: "flex-start",
701
+ gap: "10px",
702
+ margin: "0 0 16px",
703
+ fontSize: "13px",
704
+ lineHeight: "20px",
705
+ },
706
+ riskAltBox: {
707
+ margin: "0 0 16px",
708
+ padding: "12px",
709
+ borderRadius: "var(--dsw-radius-sm, 8px)",
710
+ border: "1px solid var(--dsw-alias-border-l2, #d0d5dd)",
711
+ background: "var(--dsw-alias-settings-card-fill-secondary, #f8fafc)",
712
+ },
713
+ riskAltTitle: {
714
+ margin: "0 0 6px",
715
+ fontSize: "13px",
716
+ fontWeight: 600,
717
+ lineHeight: "20px",
718
+ },
719
+ riskAltDesc: {
720
+ margin: "0 0 10px",
721
+ fontSize: "12px",
722
+ lineHeight: "18px",
723
+ color: "var(--dsw-alias-label-secondary)",
724
+ },
725
+ riskAltList: {
726
+ display: "flex",
727
+ flexDirection: "column",
728
+ gap: "8px",
729
+ },
730
+ riskAltRow: {
731
+ display: "flex",
732
+ alignItems: "center",
733
+ justifyContent: "space-between",
734
+ gap: "10px",
735
+ },
736
+ riskAltMeta: {
737
+ minWidth: 0,
738
+ flex: 1,
739
+ },
740
+ riskAltSpec: {
741
+ fontSize: "13px",
742
+ fontWeight: 500,
743
+ lineHeight: "18px",
744
+ wordBreak: "break-all",
745
+ },
746
+ riskAltHint: {
747
+ fontSize: "12px",
748
+ lineHeight: "16px",
749
+ color: "var(--dsw-alias-label-secondary)",
750
+ },
751
+ riskFooter: {
752
+ display: "flex",
753
+ justifyContent: "flex-end",
754
+ gap: "8px",
755
+ flexWrap: "wrap",
605
756
  },
606
757
  updateBox: {
607
758
  display: "flex",
@@ -751,12 +902,25 @@ window.__ModuleLoader__.load({
751
902
  const err = result.error;
752
903
  return {
753
904
  ok: false,
754
- message: (err && (err.message || err.code)) || fallbackMessage,
905
+ message: (err && (err.message || err.diagnostic || err.code)) || fallbackMessage,
755
906
  };
756
907
  }
757
908
  return result.value;
758
909
  }
759
910
 
911
+ function pickInstallError(res, fallback, lastDetail) {
912
+ const primary = String((res && (res.detail || res.message)) || "").trim();
913
+ const weak =
914
+ !primary ||
915
+ /^install failed$/i.test(primary) ||
916
+ /^安装失败([((]failed[))])?$/i.test(primary);
917
+ if (!weak) return primary;
918
+ const extra = String(lastDetail || "").trim();
919
+ const title = fallback || "安装失败";
920
+ if (extra && extra !== primary) return `${title}\n${extra}`;
921
+ return primary || title;
922
+ }
923
+
760
924
  function Panel(props) {
761
925
  const api = props.api;
762
926
  const t = props.t;
@@ -775,6 +939,15 @@ window.__ModuleLoader__.load({
775
939
  const [results, setResults] = React.useState([]);
776
940
  const [message, setMessage] = React.useState("");
777
941
  const [error, setError] = React.useState("");
942
+ const [copyHint, setCopyHint] = React.useState("");
943
+ const [riskPrompt, setRiskPrompt] = React.useState(null);
944
+ const [riskOpen, setRiskOpen] = React.useState(false);
945
+ const [riskAck, setRiskAck] = React.useState(false);
946
+ const [riskBusy, setRiskBusy] = React.useState(false);
947
+ const [buildPrompt, setBuildPrompt] = React.useState(null);
948
+ const [buildOpen, setBuildOpen] = React.useState(false);
949
+ const [buildAck, setBuildAck] = React.useState(false);
950
+ const [buildBusy, setBuildBusy] = React.useState(false);
778
951
  const [busy, setBusy] = React.useState(false);
779
952
  const [resolveInput, setResolveInput] = React.useState("");
780
953
  const [installDetail, setInstallDetail] = React.useState("");
@@ -783,6 +956,7 @@ window.__ModuleLoader__.load({
783
956
  const [page, setPage] = React.useState(1);
784
957
  const jobsRef = React.useRef(jobs);
785
958
  const installedNamesRef = React.useRef(installedNames);
959
+ const installDetailRef = React.useRef("");
786
960
  React.useEffect(() => {
787
961
  jobsRef.current = jobs;
788
962
  writeJobCache(jobs);
@@ -790,6 +964,55 @@ window.__ModuleLoader__.load({
790
964
  React.useEffect(() => {
791
965
  installedNamesRef.current = installedNames;
792
966
  }, [installedNames]);
967
+ React.useEffect(() => {
968
+ installDetailRef.current = installDetail;
969
+ }, [installDetail]);
970
+ React.useEffect(() => {
971
+ setCopyHint("");
972
+ }, [error]);
973
+
974
+ const openRiskPrompt = React.useCallback((res, item) => {
975
+ const rows = Array.isArray(res && res.incompatible) ? res.incompatible : [];
976
+ const first = rows[0];
977
+ if (!(res && res.needsRiskExemption) || !first) return;
978
+ const packageVersion = `${first.name}@${first.version}`;
979
+ const runtimeVersion = first.runtimeVersion || "unknown";
980
+ const recommendations = Array.isArray(res && res.recommendations) ? res.recommendations : [];
981
+ setRiskAck(false);
982
+ setRiskPrompt({
983
+ spec: (res && res.installSpec) || (item && (item.installSpec || item.spec)) || first.name,
984
+ item: item || null,
985
+ packageVersion,
986
+ runtimeVersion,
987
+ incompatible: rows,
988
+ recommendations,
989
+ });
990
+ setRiskOpen(true);
991
+ }, []);
992
+
993
+ const closeRiskPrompt = React.useCallback(() => {
994
+ if (riskBusy) return;
995
+ setRiskOpen(false);
996
+ setRiskAck(false);
997
+ }, [riskBusy]);
998
+
999
+ const openBuildPrompt = React.useCallback((res, item) => {
1000
+ const pending = Array.isArray(res && res.pendingBuilds) ? res.pendingBuilds.filter(Boolean) : [];
1001
+ if (!(res && res.needsBuildApproval) || !pending.length) return;
1002
+ setBuildAck(false);
1003
+ setBuildPrompt({
1004
+ spec: (res && res.installSpec) || (item && (item.installSpec || item.spec)) || "",
1005
+ item: item || null,
1006
+ pendingBuilds: pending,
1007
+ });
1008
+ setBuildOpen(true);
1009
+ }, []);
1010
+
1011
+ const closeBuildPrompt = React.useCallback(() => {
1012
+ if (buildBusy) return;
1013
+ setBuildOpen(false);
1014
+ setBuildAck(false);
1015
+ }, [buildBusy]);
793
1016
 
794
1017
  const refreshInstalled = React.useCallback(async () => {
795
1018
  if (!api || typeof api.listInstalled !== "function") return;
@@ -1045,18 +1268,38 @@ window.__ModuleLoader__.load({
1045
1268
  setMessage(res.message || t("installed"));
1046
1269
  await refreshInstalled();
1047
1270
  } else {
1271
+ const lastDetail =
1272
+ (jobsRef.current[job.requestId] && jobsRef.current[job.requestId].detail) ||
1273
+ installDetailRef.current;
1274
+ const errText = pickInstallError(res, t("installFailed"), lastDetail);
1048
1275
  finishJob(job.requestId, {
1049
1276
  status: "failed",
1050
- detail: (res && res.message) || t("installFailed"),
1277
+ detail: errText,
1051
1278
  });
1052
- setError((res && res.message) || t("installFailed"));
1279
+ setError(errText);
1280
+ const jobItem = {
1281
+ spec: job.spec,
1282
+ installSpec: job.spec,
1283
+ title: job.packageName || job.spec,
1284
+ };
1285
+ openBuildPrompt(res, jobItem);
1286
+ openRiskPrompt(res, jobItem);
1053
1287
  }
1054
1288
  } catch (e) {
1055
1289
  if (cancelled) return;
1290
+ const lastDetail =
1291
+ (jobsRef.current[job.requestId] && jobsRef.current[job.requestId].detail) ||
1292
+ installDetailRef.current;
1293
+ const errText = pickInstallError(
1294
+ { message: (e && e.message) || String(e) },
1295
+ t("installFailed"),
1296
+ lastDetail,
1297
+ );
1056
1298
  finishJob(job.requestId, {
1057
1299
  status: "failed",
1058
- detail: (e && e.message) || String(e),
1300
+ detail: errText,
1059
1301
  });
1302
+ setError(errText);
1060
1303
  } finally {
1061
1304
  if (!cancelled) setInstallDetail("");
1062
1305
  }
@@ -1151,11 +1394,22 @@ window.__ModuleLoader__.load({
1151
1394
  setMessage(installed.message || t("installed"));
1152
1395
  await refreshInstalled();
1153
1396
  } else {
1397
+ const lastDetail =
1398
+ (jobsRef.current[requestId] && jobsRef.current[requestId].detail) ||
1399
+ installDetailRef.current;
1400
+ const errText = pickInstallError(installed, t("installFailed"), lastDetail);
1154
1401
  finishJob(requestId, {
1155
1402
  status: "failed",
1156
- detail: (installed && installed.message) || t("inspectFailed"),
1403
+ detail: errText,
1157
1404
  });
1158
- setError((installed && installed.message) || t("inspectFailed"));
1405
+ setError(errText);
1406
+ const resolveItem = {
1407
+ spec: res.spec,
1408
+ installSpec: res.spec,
1409
+ title: (res.inspection && res.inspection.name) || res.spec,
1410
+ };
1411
+ openBuildPrompt(installed, resolveItem);
1412
+ openRiskPrompt(installed, resolveItem);
1159
1413
  }
1160
1414
  } else {
1161
1415
  setResults([]);
@@ -1169,10 +1423,14 @@ window.__ModuleLoader__.load({
1169
1423
  }
1170
1424
  };
1171
1425
 
1172
- const onInstall = async (item) => {
1426
+ const onInstall = async (item, installOpts) => {
1173
1427
  const spec = item.installSpec || item.spec;
1428
+ const approvedBuilds = Array.isArray(installOpts && installOpts.approvedBuilds)
1429
+ ? installOpts.approvedBuilds.filter(Boolean)
1430
+ : [];
1174
1431
  const current = resolveItemStatus(item, installedNames, jobs);
1175
- if (!spec || current.status === "installed" || current.status === "installing") return;
1432
+ if (!spec || current.status === "installed") return;
1433
+ if (current.status === "installing" && !approvedBuilds.length) return;
1176
1434
  const requestId = newRequestId();
1177
1435
  upsertJob(requestId, {
1178
1436
  status: "installing",
@@ -1185,7 +1443,10 @@ window.__ModuleLoader__.load({
1185
1443
  setMessage("");
1186
1444
  setError("");
1187
1445
  try {
1188
- const res = await api.install(spec, { requestId });
1446
+ const res = await api.install(spec, {
1447
+ requestId,
1448
+ ...(approvedBuilds.length ? { approvedBuilds } : {}),
1449
+ });
1189
1450
  if (res && res.alreadyInstalled) {
1190
1451
  finishJob(requestId, {
1191
1452
  status: "installed",
@@ -1204,23 +1465,103 @@ window.__ModuleLoader__.load({
1204
1465
  await refreshInstalled();
1205
1466
  setMessage(res.message || t("installed"));
1206
1467
  } else {
1468
+ const lastDetail =
1469
+ (jobsRef.current[requestId] && jobsRef.current[requestId].detail) || installDetailRef.current;
1470
+ const errText = pickInstallError(res, t("installFailed"), lastDetail);
1207
1471
  finishJob(requestId, {
1208
1472
  status: "failed",
1209
- detail: (res && res.message) || t("installFailed"),
1473
+ detail: errText,
1210
1474
  });
1211
- setError((res && res.message) || t("installFailed"));
1475
+ setError(errText);
1476
+ openBuildPrompt(res, item);
1477
+ openRiskPrompt(res, item);
1212
1478
  }
1213
1479
  } catch (e) {
1480
+ const lastDetail =
1481
+ (jobsRef.current[requestId] && jobsRef.current[requestId].detail) || installDetailRef.current;
1482
+ const errText = pickInstallError(
1483
+ { message: (e && e.message) || String(e) },
1484
+ t("installFailed"),
1485
+ lastDetail,
1486
+ );
1214
1487
  finishJob(requestId, {
1215
1488
  status: "failed",
1216
- detail: (e && e.message) || String(e),
1489
+ detail: errText,
1217
1490
  });
1218
- setError((e && e.message) || String(e));
1491
+ setError(errText);
1219
1492
  } finally {
1220
1493
  setInstallDetail("");
1221
1494
  }
1222
1495
  };
1223
1496
 
1497
+ const confirmRiskInstall = async () => {
1498
+ if (!riskPrompt || !riskAck || !api || typeof api.grantVersionExemption !== "function") return;
1499
+ setRiskBusy(true);
1500
+ setMessage("");
1501
+ try {
1502
+ const granted = await api.grantVersionExemption(
1503
+ riskPrompt.packageVersion,
1504
+ riskPrompt.runtimeVersion,
1505
+ true,
1506
+ );
1507
+ if (!granted || !granted.ok) {
1508
+ setError((granted && granted.message) || t("installFailed"));
1509
+ return;
1510
+ }
1511
+ const item = riskPrompt.item || {
1512
+ spec: riskPrompt.spec,
1513
+ installSpec: riskPrompt.spec,
1514
+ title: riskPrompt.packageVersion,
1515
+ };
1516
+ setRiskOpen(false);
1517
+ setRiskAck(false);
1518
+ setError("");
1519
+ await onInstall(item);
1520
+ } catch (e) {
1521
+ setError((e && e.message) || String(e));
1522
+ } finally {
1523
+ setRiskBusy(false);
1524
+ }
1525
+ };
1526
+
1527
+ const confirmBuildInstall = async () => {
1528
+ if (!buildPrompt || !buildAck || !api) return;
1529
+ const pending = Array.isArray(buildPrompt.pendingBuilds) ? buildPrompt.pendingBuilds.filter(Boolean) : [];
1530
+ if (!pending.length) return;
1531
+ setBuildBusy(true);
1532
+ setMessage("");
1533
+ try {
1534
+ const item = buildPrompt.item || {
1535
+ spec: buildPrompt.spec,
1536
+ installSpec: buildPrompt.spec,
1537
+ title: buildPrompt.spec,
1538
+ };
1539
+ setBuildOpen(false);
1540
+ setBuildAck(false);
1541
+ setError("");
1542
+ await onInstall(item, { approvedBuilds: pending });
1543
+ } catch (e) {
1544
+ setError((e && e.message) || String(e));
1545
+ } finally {
1546
+ setBuildBusy(false);
1547
+ }
1548
+ };
1549
+
1550
+ const installRecommendedAlt = async (alt) => {
1551
+ if (!alt || riskBusy) return;
1552
+ const item = {
1553
+ spec: alt.installSpec || alt.spec,
1554
+ installSpec: alt.installSpec || alt.spec,
1555
+ title: alt.title || alt.spec,
1556
+ source: alt.source || "catalog",
1557
+ catalogId: alt.catalogId,
1558
+ };
1559
+ setRiskOpen(false);
1560
+ setRiskAck(false);
1561
+ setError("");
1562
+ await onInstall(item);
1563
+ };
1564
+
1224
1565
  const sourceOptions = [
1225
1566
  { value: "all", label: t("sourceAll") },
1226
1567
  { value: "npm", label: t("sourceNpm") },
@@ -1334,6 +1675,9 @@ window.__ModuleLoader__.load({
1334
1675
  : t("selfUpdateOriginNpm");
1335
1676
 
1336
1677
  return h(
1678
+ React.Fragment,
1679
+ null,
1680
+ h(
1337
1681
  "div",
1338
1682
  { style: styles.page },
1339
1683
  h(
@@ -1501,16 +1845,67 @@ window.__ModuleLoader__.load({
1501
1845
  ? h(
1502
1846
  "div",
1503
1847
  { style: styles.errorBox },
1504
- h("span", null, error),
1848
+ h("div", { style: styles.errorText }, error),
1505
1849
  h(
1506
- Btn,
1507
- {
1508
- size: "sm",
1509
- variant: "outline",
1510
- disabled: busy,
1511
- onClick: onSearch,
1512
- },
1513
- t("retry"),
1850
+ "div",
1851
+ { style: styles.errorActions },
1852
+ h(
1853
+ Btn,
1854
+ {
1855
+ size: "sm",
1856
+ variant: "outline",
1857
+ disabled: busy || riskBusy,
1858
+ onClick: async () => {
1859
+ const text = String(error || "");
1860
+ try {
1861
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1862
+ await navigator.clipboard.writeText(text);
1863
+ } else {
1864
+ const ta = document.createElement("textarea");
1865
+ ta.value = text;
1866
+ ta.setAttribute("readonly", "");
1867
+ ta.style.position = "fixed";
1868
+ ta.style.left = "-9999px";
1869
+ document.body.appendChild(ta);
1870
+ ta.select();
1871
+ document.execCommand("copy");
1872
+ document.body.removeChild(ta);
1873
+ }
1874
+ setCopyHint(t("copied"));
1875
+ window.setTimeout(() => setCopyHint(""), 1600);
1876
+ } catch {
1877
+ setCopyHint(t("copyFailed"));
1878
+ window.setTimeout(() => setCopyHint(""), 1600);
1879
+ }
1880
+ },
1881
+ },
1882
+ copyHint || t("copy"),
1883
+ ),
1884
+ riskPrompt
1885
+ ? h(
1886
+ Btn,
1887
+ {
1888
+ size: "sm",
1889
+ variant: "outline",
1890
+ disabled: busy || riskBusy,
1891
+ onClick: () => {
1892
+ setRiskAck(false);
1893
+ setRiskOpen(true);
1894
+ },
1895
+ },
1896
+ t("riskInstall"),
1897
+ )
1898
+ : null,
1899
+ h(
1900
+ Btn,
1901
+ {
1902
+ size: "sm",
1903
+ variant: "outline",
1904
+ disabled: busy || riskBusy,
1905
+ onClick: onSearch,
1906
+ },
1907
+ t("retry"),
1908
+ ),
1514
1909
  ),
1515
1910
  )
1516
1911
  : null,
@@ -1634,6 +2029,182 @@ window.__ModuleLoader__.load({
1634
2029
  )
1635
2030
  : null,
1636
2031
  ),
2032
+ ),
2033
+ buildOpen && buildPrompt
2034
+ ? h(
2035
+ "div",
2036
+ {
2037
+ style: styles.riskOverlay,
2038
+ onClick: (e) => {
2039
+ if (e.target === e.currentTarget) closeBuildPrompt();
2040
+ },
2041
+ },
2042
+ h(
2043
+ "div",
2044
+ {
2045
+ style: styles.riskDialog,
2046
+ role: "dialog",
2047
+ "aria-modal": "true",
2048
+ onClick: (e) => e.stopPropagation(),
2049
+ },
2050
+ h("h2", { style: styles.riskTitle }, t("buildTitle")),
2051
+ h(
2052
+ "p",
2053
+ { style: styles.riskDesc },
2054
+ format(t("buildDesc"), { spec: buildPrompt.spec || "" }),
2055
+ ),
2056
+ h(
2057
+ "div",
2058
+ { style: styles.riskAltBox },
2059
+ h(
2060
+ "div",
2061
+ { style: styles.riskAltList },
2062
+ ...(Array.isArray(buildPrompt.pendingBuilds) ? buildPrompt.pendingBuilds : []).map(
2063
+ (name) =>
2064
+ h("div", { key: name, style: styles.riskAltSpec }, name),
2065
+ ),
2066
+ ),
2067
+ ),
2068
+ h(
2069
+ "label",
2070
+ { style: styles.riskAckRow },
2071
+ h("input", {
2072
+ type: "checkbox",
2073
+ checked: buildAck,
2074
+ disabled: buildBusy,
2075
+ onChange: (e) => setBuildAck(!!e.target.checked),
2076
+ }),
2077
+ h("span", null, t("buildAck")),
2078
+ ),
2079
+ h(
2080
+ "div",
2081
+ { style: styles.riskFooter },
2082
+ h(
2083
+ Btn,
2084
+ {
2085
+ size: "sm",
2086
+ variant: "outline",
2087
+ disabled: buildBusy,
2088
+ onClick: closeBuildPrompt,
2089
+ },
2090
+ t("buildCancel"),
2091
+ ),
2092
+ h(
2093
+ Btn,
2094
+ {
2095
+ size: "sm",
2096
+ disabled: buildBusy || !buildAck,
2097
+ onClick: () => void confirmBuildInstall(),
2098
+ },
2099
+ buildBusy ? t("buildWorking") : t("buildConfirm"),
2100
+ ),
2101
+ ),
2102
+ ),
2103
+ )
2104
+ : null,
2105
+ riskOpen && riskPrompt
2106
+ ? h(
2107
+ "div",
2108
+ {
2109
+ style: styles.riskOverlay,
2110
+ onClick: (e) => {
2111
+ if (e.target === e.currentTarget) closeRiskPrompt();
2112
+ },
2113
+ },
2114
+ h(
2115
+ "div",
2116
+ {
2117
+ style: styles.riskDialog,
2118
+ role: "dialog",
2119
+ "aria-modal": "true",
2120
+ onClick: (e) => e.stopPropagation(),
2121
+ },
2122
+ h("h2", { style: styles.riskTitle }, t("riskTitle")),
2123
+ h(
2124
+ "p",
2125
+ { style: styles.riskDesc },
2126
+ format(t("riskDesc"), {
2127
+ package: riskPrompt.packageVersion,
2128
+ runtime: riskPrompt.runtimeVersion,
2129
+ }),
2130
+ ),
2131
+ Array.isArray(riskPrompt.recommendations) && riskPrompt.recommendations.length
2132
+ ? h(
2133
+ "div",
2134
+ { style: styles.riskAltBox },
2135
+ h("div", { style: styles.riskAltTitle }, t("riskAltTitle")),
2136
+ h("p", { style: styles.riskAltDesc }, t("riskAltDesc")),
2137
+ h(
2138
+ "div",
2139
+ { style: styles.riskAltList },
2140
+ ...riskPrompt.recommendations.map((alt) =>
2141
+ h(
2142
+ "div",
2143
+ { key: alt.spec, style: styles.riskAltRow },
2144
+ h(
2145
+ "div",
2146
+ { style: styles.riskAltMeta },
2147
+ h("div", { style: styles.riskAltSpec }, alt.title || alt.spec),
2148
+ h(
2149
+ "div",
2150
+ { style: styles.riskAltHint },
2151
+ alt.spec === (alt.title || "")
2152
+ ? alt.reason || ""
2153
+ : alt.spec,
2154
+ ),
2155
+ ),
2156
+ h(
2157
+ Btn,
2158
+ {
2159
+ size: "sm",
2160
+ variant: "outline",
2161
+ disabled: riskBusy,
2162
+ onClick: () => void installRecommendedAlt(alt),
2163
+ },
2164
+ t("riskAltInstall"),
2165
+ ),
2166
+ ),
2167
+ ),
2168
+ ),
2169
+ )
2170
+ : null,
2171
+ h(
2172
+ "label",
2173
+ { style: styles.riskAckRow },
2174
+ h("input", {
2175
+ type: "checkbox",
2176
+ checked: riskAck,
2177
+ disabled: riskBusy,
2178
+ onChange: (e) => setRiskAck(!!e.target.checked),
2179
+ }),
2180
+ h("span", null, t("riskAck")),
2181
+ ),
2182
+ h(
2183
+ "div",
2184
+ { style: styles.riskFooter },
2185
+ h(
2186
+ Btn,
2187
+ {
2188
+ size: "sm",
2189
+ variant: "outline",
2190
+ disabled: riskBusy,
2191
+ onClick: closeRiskPrompt,
2192
+ },
2193
+ t("riskCancel"),
2194
+ ),
2195
+ h(
2196
+ Btn,
2197
+ {
2198
+ size: "sm",
2199
+ disabled: riskBusy || !riskAck,
2200
+ onClick: () => void confirmRiskInstall(),
2201
+ },
2202
+ riskBusy ? t("riskWorking") : t("riskConfirm"),
2203
+ ),
2204
+ ),
2205
+ ),
2206
+ )
2207
+ : null,
1637
2208
  );
1638
2209
  }
1639
2210
 
@@ -1651,6 +2222,10 @@ window.__ModuleLoader__.load({
1651
2222
  const SPEC_CODEC = strictJson("dsh-plugin-discovery#pluginDiscovery/installPlugin:spec");
1652
2223
  const INSTALL_OPTIONS_CODEC = strictJson("dsh-plugin-discovery#pluginDiscovery/installPlugin:options");
1653
2224
  const INSTALL_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/installPlugin:result");
2225
+ const GRANT_PACKAGE = strictJson("dsh-plugin-discovery#pluginDiscovery/grantVersionExemption:packageVersion");
2226
+ const GRANT_RUNTIME = strictJson("dsh-plugin-discovery#pluginDiscovery/grantVersionExemption:runtimeVersion");
2227
+ const GRANT_ACCEPT = strictJson("dsh-plugin-discovery#pluginDiscovery/grantVersionExemption:acceptRisk");
2228
+ const GRANT_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/grantVersionExemption:result");
1654
2229
  const LIST_INSTALLED_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/listInstalled:result");
1655
2230
  const WAIT_REQUEST_ID = strictJson("dsh-plugin-discovery#pluginDiscovery/waitInstall:requestId");
1656
2231
  const WAIT_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/waitInstall:result");
@@ -1754,6 +2329,19 @@ window.__ModuleLoader__.load({
1754
2329
  ],
1755
2330
  result: INSTALL_RESULT,
1756
2331
  },
2332
+ {
2333
+ id: "dsh-plugin-discovery#pluginDiscovery/grantVersionExemption",
2334
+ service: "pluginDiscovery",
2335
+ namespace: "pluginDiscovery",
2336
+ method: "grantVersionExemption",
2337
+ invocation: { kind: "direct" },
2338
+ parameters: [
2339
+ { name: "packageVersion", wire: "packageVersion", source: "json", codec: GRANT_PACKAGE },
2340
+ { name: "runtimeVersion", wire: "runtimeVersion", source: "json", codec: GRANT_RUNTIME },
2341
+ { name: "acceptRisk", wire: "acceptRisk", source: "json", codec: GRANT_ACCEPT },
2342
+ ],
2343
+ result: GRANT_RESULT,
2344
+ },
1757
2345
  {
1758
2346
  id: "dsh-plugin-discovery#pluginDiscovery/listInstalled",
1759
2347
  service: "pluginDiscovery",
@@ -1875,10 +2463,29 @@ window.__ModuleLoader__.load({
1875
2463
  packageName: value && value.packageName,
1876
2464
  requestId: value && value.requestId,
1877
2465
  message: value && value.message,
2466
+ detail: value && value.detail,
2467
+ code: value && value.code,
2468
+ incompatible: value && value.incompatible,
2469
+ needsRiskExemption: !!(value && value.needsRiskExemption),
2470
+ pendingBuilds: value && value.pendingBuilds,
2471
+ needsBuildApproval: !!(value && value.needsBuildApproval),
2472
+ recommendations: value && value.recommendations,
2473
+ installSpec: (value && value.installSpec) || spec,
1878
2474
  };
1879
2475
  }
1880
2476
  return value;
1881
2477
  },
2478
+ grantVersionExemption: async (packageVersion, runtimeVersion, acceptRisk) => {
2479
+ if (mountError) return { ok: false, message: mountError };
2480
+ const remote = remoteNs();
2481
+ if (!remote || typeof remote.grantVersionExemption !== "function") {
2482
+ return { ok: false, message: "remote.pluginDiscovery.grantVersionExemption unavailable" };
2483
+ }
2484
+ const wire = await remote.grantVersionExemption(packageVersion, runtimeVersion, acceptRisk);
2485
+ const value = unwrapRemote(wire, "grantVersionExemption failed");
2486
+ if (wire && wire.ok === false) return { ok: false, message: value.message };
2487
+ return value;
2488
+ },
1882
2489
  listInstalled: async () => {
1883
2490
  if (mountError) return { ok: false, names: [] };
1884
2491
  const remote = remoteNs();
@@ -1898,7 +2505,22 @@ window.__ModuleLoader__.load({
1898
2505
  }
1899
2506
  const wire = await remote.waitInstall(requestId);
1900
2507
  const value = unwrapRemote(wire, "waitInstall failed");
1901
- if (wire && wire.ok === false) return { ok: false, message: value.message || "waitInstall failed" };
2508
+ if (wire && wire.ok === false) {
2509
+ return {
2510
+ ok: false,
2511
+ message: (value && value.message) || "waitInstall failed",
2512
+ detail: value && value.detail,
2513
+ code: value && value.code,
2514
+ incompatible: value && value.incompatible,
2515
+ needsRiskExemption: !!(value && value.needsRiskExemption),
2516
+ pendingBuilds: value && value.pendingBuilds,
2517
+ needsBuildApproval: !!(value && value.needsBuildApproval),
2518
+ recommendations: value && value.recommendations,
2519
+ installSpec: value && value.installSpec,
2520
+ packageName: value && value.packageName,
2521
+ pending: !!(value && value.pending),
2522
+ };
2523
+ }
1902
2524
  return value;
1903
2525
  },
1904
2526
  subscribePluginChanged: (listener) => {