dsh-plugin-discovery 1.2.4 → 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 (4) hide show
  1. package/README.md +2 -1
  2. package/client.js +154 -7
  3. package/index.js +39 -2
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -52,7 +52,7 @@ github:hjhcos/dsh-plugin-discovery
52
52
 
53
53
  详见 [`CHANGELOG.md`](./CHANGELOG.md)。每次发版会同步推送 GitHub / Gitee 并发布 npm。
54
54
 
55
- 当前:**1.2.4** — 修复风险豁免 Typert 签名;版本不兼容时推荐相近/兼容插件。
55
+ 当前:**1.2.5** — 构建脚本拦截时可在面板内确认批准并重试;版本不兼容时推荐相近插件。
56
56
 
57
57
  ## 功能
58
58
 
@@ -62,6 +62,7 @@ github:hjhcos/dsh-plugin-discovery
62
62
  - **远程目录更新**:优先 Gitee raw JSON,失败再试 GitHub;按 `version` 写入 `$DSH_HOME/cache/...`
63
63
  - **插件自更新**:启动/打开面板时检查 npm 最新版(Gitee `package.json` 兜底);有新版本可自动或一键更新
64
64
  - **不兼容引导**:安装 peer 不匹配时展示真实错误、复制、风险豁免重试,并推荐精选替代包
65
+ - **构建脚本批准**:pnpm 拦截依赖 install 脚本(如 `ssh2`)时,可在面板确认后代为批准并重试
65
66
  - 关键词搜索(全部 / npm / GitHub)与链接解析安装
66
67
  - 安装三态:未安装 / 安装中 / 已安装
67
68
  - 中英文界面
package/client.js CHANGED
@@ -63,6 +63,13 @@ window.__ModuleLoader__.load({
63
63
  riskAltTitle: "推荐改用兼容插件",
64
64
  riskAltDesc: "以下精选包更可能匹配当前 DSH 版本,可直接安装代替强行豁免:",
65
65
  riskAltInstall: "安装推荐",
66
+ buildTitle: "需要批准依赖构建脚本",
67
+ buildDesc:
68
+ "安装「{spec}」时,pnpm 拦截了以下依赖的安装脚本(常用于编译原生模块)。批准后将以你的用户权限执行这些脚本,并重试安装。",
69
+ buildAck: "我已了解风险,允许这些依赖运行安装脚本并重试",
70
+ buildCancel: "取消",
71
+ buildConfirm: "批准并重试安装",
72
+ buildWorking: "正在批准并重试…",
66
73
  browseRecommend: "为你推荐",
67
74
  browseCategory: "分类浏览",
68
75
  browseSearch: "在线搜索",
@@ -157,6 +164,13 @@ window.__ModuleLoader__.load({
157
164
  riskAltTitle: "Try a compatible plugin instead",
158
165
  riskAltDesc: "These curated packages are more likely to match this DSH version:",
159
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…",
160
174
  browseRecommend: "For you",
161
175
  browseCategory: "Categories",
162
176
  browseSearch: "Online search",
@@ -930,6 +944,10 @@ window.__ModuleLoader__.load({
930
944
  const [riskOpen, setRiskOpen] = React.useState(false);
931
945
  const [riskAck, setRiskAck] = React.useState(false);
932
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);
933
951
  const [busy, setBusy] = React.useState(false);
934
952
  const [resolveInput, setResolveInput] = React.useState("");
935
953
  const [installDetail, setInstallDetail] = React.useState("");
@@ -978,6 +996,24 @@ window.__ModuleLoader__.load({
978
996
  setRiskAck(false);
979
997
  }, [riskBusy]);
980
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]);
1016
+
981
1017
  const refreshInstalled = React.useCallback(async () => {
982
1018
  if (!api || typeof api.listInstalled !== "function") return;
983
1019
  try {
@@ -1241,11 +1277,13 @@ window.__ModuleLoader__.load({
1241
1277
  detail: errText,
1242
1278
  });
1243
1279
  setError(errText);
1244
- openRiskPrompt(res, {
1280
+ const jobItem = {
1245
1281
  spec: job.spec,
1246
1282
  installSpec: job.spec,
1247
1283
  title: job.packageName || job.spec,
1248
- });
1284
+ };
1285
+ openBuildPrompt(res, jobItem);
1286
+ openRiskPrompt(res, jobItem);
1249
1287
  }
1250
1288
  } catch (e) {
1251
1289
  if (cancelled) return;
@@ -1365,11 +1403,13 @@ window.__ModuleLoader__.load({
1365
1403
  detail: errText,
1366
1404
  });
1367
1405
  setError(errText);
1368
- openRiskPrompt(installed, {
1406
+ const resolveItem = {
1369
1407
  spec: res.spec,
1370
1408
  installSpec: res.spec,
1371
1409
  title: (res.inspection && res.inspection.name) || res.spec,
1372
- });
1410
+ };
1411
+ openBuildPrompt(installed, resolveItem);
1412
+ openRiskPrompt(installed, resolveItem);
1373
1413
  }
1374
1414
  } else {
1375
1415
  setResults([]);
@@ -1383,10 +1423,14 @@ window.__ModuleLoader__.load({
1383
1423
  }
1384
1424
  };
1385
1425
 
1386
- const onInstall = async (item) => {
1426
+ const onInstall = async (item, installOpts) => {
1387
1427
  const spec = item.installSpec || item.spec;
1428
+ const approvedBuilds = Array.isArray(installOpts && installOpts.approvedBuilds)
1429
+ ? installOpts.approvedBuilds.filter(Boolean)
1430
+ : [];
1388
1431
  const current = resolveItemStatus(item, installedNames, jobs);
1389
- if (!spec || current.status === "installed" || current.status === "installing") return;
1432
+ if (!spec || current.status === "installed") return;
1433
+ if (current.status === "installing" && !approvedBuilds.length) return;
1390
1434
  const requestId = newRequestId();
1391
1435
  upsertJob(requestId, {
1392
1436
  status: "installing",
@@ -1399,7 +1443,10 @@ window.__ModuleLoader__.load({
1399
1443
  setMessage("");
1400
1444
  setError("");
1401
1445
  try {
1402
- const res = await api.install(spec, { requestId });
1446
+ const res = await api.install(spec, {
1447
+ requestId,
1448
+ ...(approvedBuilds.length ? { approvedBuilds } : {}),
1449
+ });
1403
1450
  if (res && res.alreadyInstalled) {
1404
1451
  finishJob(requestId, {
1405
1452
  status: "installed",
@@ -1426,6 +1473,7 @@ window.__ModuleLoader__.load({
1426
1473
  detail: errText,
1427
1474
  });
1428
1475
  setError(errText);
1476
+ openBuildPrompt(res, item);
1429
1477
  openRiskPrompt(res, item);
1430
1478
  }
1431
1479
  } catch (e) {
@@ -1476,6 +1524,29 @@ window.__ModuleLoader__.load({
1476
1524
  }
1477
1525
  };
1478
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
+
1479
1550
  const installRecommendedAlt = async (alt) => {
1480
1551
  if (!alt || riskBusy) return;
1481
1552
  const item = {
@@ -1959,6 +2030,78 @@ window.__ModuleLoader__.load({
1959
2030
  : null,
1960
2031
  ),
1961
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,
1962
2105
  riskOpen && riskPrompt
1963
2106
  ? h(
1964
2107
  "div",
@@ -2324,6 +2467,8 @@ window.__ModuleLoader__.load({
2324
2467
  code: value && value.code,
2325
2468
  incompatible: value && value.incompatible,
2326
2469
  needsRiskExemption: !!(value && value.needsRiskExemption),
2470
+ pendingBuilds: value && value.pendingBuilds,
2471
+ needsBuildApproval: !!(value && value.needsBuildApproval),
2327
2472
  recommendations: value && value.recommendations,
2328
2473
  installSpec: (value && value.installSpec) || spec,
2329
2474
  };
@@ -2368,6 +2513,8 @@ window.__ModuleLoader__.load({
2368
2513
  code: value && value.code,
2369
2514
  incompatible: value && value.incompatible,
2370
2515
  needsRiskExemption: !!(value && value.needsRiskExemption),
2516
+ pendingBuilds: value && value.pendingBuilds,
2517
+ needsBuildApproval: !!(value && value.needsBuildApproval),
2371
2518
  recommendations: value && value.recommendations,
2372
2519
  installSpec: value && value.installSpec,
2373
2520
  packageName: value && value.packageName,
package/index.js CHANGED
@@ -501,6 +501,28 @@ function extractIncompatibility(result) {
501
501
  };
502
502
  }
503
503
 
504
+ /** pnpm 11 拦截依赖安装脚本时,失败结果带 pendingBuilds;需用户确认后经 approvedBuilds 重试。 */
505
+ function extractBuildBlock(result) {
506
+ const pendingBuilds = Array.isArray(result?.pendingBuilds)
507
+ ? [...new Set(result.pendingBuilds.map((name) => String(name || "").trim()).filter(Boolean))]
508
+ : [];
509
+ const kind = result?.packageResult?.kind || null;
510
+ const code = result?.error?.code || null;
511
+ const blocked =
512
+ kind === "build-blocked" ||
513
+ code === "build-blocked" ||
514
+ /ERR_PNPM_IGNORED_BUILDS|Ignored build scripts/i.test(String(result?.packageResult?.output || ""));
515
+ return {
516
+ pendingBuilds,
517
+ needsBuildApproval: blocked && pendingBuilds.length > 0,
518
+ };
519
+ }
520
+
521
+ function normalizeApprovedBuilds(value) {
522
+ if (!Array.isArray(value)) return [];
523
+ return [...new Set(value.map((name) => String(name || "").trim()).filter(Boolean))];
524
+ }
525
+
504
526
  /** 安装失败时从精选目录推荐相近/同标签插件,优先给出可替代的当前生态包。 */
505
527
  function buildFailureRecommendations(catalog, failedSpec, limit = 4) {
506
528
  const failed = String(failedSpec || "").trim();
@@ -1269,10 +1291,12 @@ class PluginDiscoveryService extends TypertRemoteService {
1269
1291
  }
1270
1292
  return `${text}。可改试精选目录中的相近插件:${alts.join("、")}`;
1271
1293
  };
1294
+ const approvedBuilds = normalizeApprovedBuilds(opts.approvedBuilds);
1272
1295
  try {
1273
1296
  const result = await this.ctx.pluginManager.installBundle(trimmed, {
1274
1297
  requestId,
1275
1298
  enabled: true,
1299
+ ...(approvedBuilds.length ? { approvedBuilds } : {}),
1276
1300
  });
1277
1301
  const packageName = result?.bundle || pre.name || trimmed;
1278
1302
  const application = result?.application;
@@ -1281,6 +1305,7 @@ class PluginDiscoveryService extends TypertRemoteService {
1281
1305
  ? formatChangeFailure(result, application || "failed")
1282
1306
  : `「${packageName}」安装完成(${application || "applied"} / ${result?.stage || "install"})`;
1283
1307
  const incompat = failed ? extractIncompatibility(result) : {};
1308
+ const buildBlock = failed ? extractBuildBlock(result) : {};
1284
1309
  const recommendations =
1285
1310
  failed && (incompat.needsRiskExemption || incompat.code === "incompatible-version")
1286
1311
  ? buildFailureRecommendations(this.getCatalog(), trimmed, 4)
@@ -1299,9 +1324,15 @@ class PluginDiscoveryService extends TypertRemoteService {
1299
1324
  message,
1300
1325
  detail: failed ? rawMessage : undefined,
1301
1326
  logPath: failed ? result?.packageResult?.logPath : undefined,
1302
- code: incompat.code || undefined,
1327
+ code:
1328
+ incompat.code ||
1329
+ (buildBlock.needsBuildApproval ? "build-blocked" : undefined) ||
1330
+ undefined,
1303
1331
  incompatible: incompat.incompatible || undefined,
1304
1332
  needsRiskExemption: incompat.needsRiskExemption === true,
1333
+ pendingBuilds: buildBlock.pendingBuilds || undefined,
1334
+ needsBuildApproval: buildBlock.needsBuildApproval === true,
1335
+ approvedBuilds: approvedBuilds.length ? approvedBuilds : result?.approvedBuilds,
1305
1336
  recommendations,
1306
1337
  installSpec: trimmed,
1307
1338
  result,
@@ -1371,6 +1402,7 @@ class PluginDiscoveryService extends TypertRemoteService {
1371
1402
  const failed = settled.application === "failed" || settled.application === "cancelled";
1372
1403
  const failText = failed ? formatChangeFailure(settled, settled.application) : "";
1373
1404
  const incompat = failed ? extractIncompatibility(settled) : {};
1405
+ const buildBlock = failed ? extractBuildBlock(settled) : {};
1374
1406
  const failedSpec =
1375
1407
  (Array.isArray(incompat.incompatible) && incompat.incompatible[0]
1376
1408
  ? `${incompat.incompatible[0].name}@${incompat.incompatible[0].version}`
@@ -1395,9 +1427,14 @@ class PluginDiscoveryService extends TypertRemoteService {
1395
1427
  message,
1396
1428
  detail: failed ? failText : undefined,
1397
1429
  logPath: failed ? settled?.packageResult?.logPath : undefined,
1398
- code: incompat.code || undefined,
1430
+ code:
1431
+ incompat.code ||
1432
+ (buildBlock.needsBuildApproval ? "build-blocked" : undefined) ||
1433
+ undefined,
1399
1434
  incompatible: incompat.incompatible || undefined,
1400
1435
  needsRiskExemption: incompat.needsRiskExemption === true,
1436
+ pendingBuilds: buildBlock.pendingBuilds || undefined,
1437
+ needsBuildApproval: buildBlock.needsBuildApproval === true,
1401
1438
  recommendations,
1402
1439
  installSpec: failedSpec || undefined,
1403
1440
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-discovery",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "type": "module",
5
5
  "description": "Discover and install community DSH bundles from the sidebar: newbie essentials, recommendations, categories, self-update, and npm/GitHub search.",
6
6
  "author": "hjhcos",