@1e0zj/dsh-plugin-mall 0.1.17 → 0.2.0
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.
- package/README.md +92 -6
- package/package.json +6 -2
- package/src/cli.js +1638 -0
- package/src/client.js +213 -57
- package/src/github.js +121 -26
- package/src/guard.js +2413 -0
- package/src/index.js +1650 -88
- package/src/installer.js +1585 -77
package/src/client.js
CHANGED
|
@@ -49,6 +49,7 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
".mkt_ok{color:var(--dsw-alias-state-success-primary,#2f855a)}",
|
|
50
50
|
".mkt_badge{display:inline-block;border-radius:999px;padding:1px 8px;font-size:11px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}",
|
|
51
51
|
".mkt_badgeOk{border-color:var(--dsw-alias-state-success-primary,#2f855a);color:var(--dsw-alias-state-success-primary,#2f855a)}",
|
|
52
|
+
".mkt_badgeWarn{border-color:var(--dsw-alias-state-warning-primary,#b7791f);color:var(--dsw-alias-state-warning-primary,#b7791f)}",
|
|
52
53
|
".mkt_badgeBad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}",
|
|
53
54
|
".mkt_check{display:flex;align-items:center;gap:4px;font-size:12.5px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap}",
|
|
54
55
|
".mkt_link{color:var(--dsw-alias-state-business-primary);font-size:12px;text-decoration:none;cursor:pointer}",
|
|
@@ -64,6 +65,12 @@ window.__ModuleLoader__.load({
|
|
|
64
65
|
".mkt_approveName{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin-right:8px;overflow-wrap:anywhere}",
|
|
65
66
|
".mkt_approveCmd{max-height:none;margin:0}",
|
|
66
67
|
".mkt_jobDone{display:flex;align-items:center;gap:8px;flex-wrap:wrap}",
|
|
68
|
+
".mkt_issueList{list-style:none;display:flex;flex-direction:column;gap:8px;margin:0;padding:0}",
|
|
69
|
+
".mkt_issue{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 10px;background:var(--dsw-alias-bg-secondary,#fff)}",
|
|
70
|
+
".mkt_issueBlock{border-color:var(--dsw-alias-state-error-primary)}",
|
|
71
|
+
".mkt_issueWarn{border-color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-state-business-primary))}",
|
|
72
|
+
".mkt_issueTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}",
|
|
73
|
+
".mkt_issueDetail{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:4px;line-height:18px;overflow-wrap:anywhere}",
|
|
67
74
|
].join("\n");
|
|
68
75
|
var tagId = "@1e0zj/dsh-plugin-mall/market-tab.css";
|
|
69
76
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
@@ -97,8 +104,26 @@ window.__ModuleLoader__.load({
|
|
|
97
104
|
return "普通依赖";
|
|
98
105
|
}
|
|
99
106
|
|
|
107
|
+
function generateSessionNonce() {
|
|
108
|
+
if (typeof crypto !== "undefined") {
|
|
109
|
+
if (typeof crypto.randomUUID === "function") {
|
|
110
|
+
return "sess_" + crypto.randomUUID();
|
|
111
|
+
}
|
|
112
|
+
if (typeof crypto.getRandomValues === "function") {
|
|
113
|
+
var bytes = new Uint8Array(16);
|
|
114
|
+
crypto.getRandomValues(bytes);
|
|
115
|
+
var hex = "";
|
|
116
|
+
for (var i = 0; i < bytes.length; i++) {
|
|
117
|
+
hex += (bytes[i] < 16 ? "0" : "") + bytes[i].toString(16);
|
|
118
|
+
}
|
|
119
|
+
return "sess_" + hex;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw new Error("Secure browser randomness is unavailable; plugin marketplace actions are disabled");
|
|
123
|
+
}
|
|
124
|
+
|
|
100
125
|
// ── job polling ─────────────────────────────────────────────────────────
|
|
101
|
-
function useJobPolling(call, onSettled) {
|
|
126
|
+
function useJobPolling(call, onSettled, onApprovalToken) {
|
|
102
127
|
var jobsRef = useRef({});
|
|
103
128
|
var _jobs = useState({});
|
|
104
129
|
var jobs = _jobs[0];
|
|
@@ -117,14 +142,18 @@ window.__ModuleLoader__.load({
|
|
|
117
142
|
var old = jobsRef.current[id] || {};
|
|
118
143
|
var wasTerminal = old.status === "completed" || old.status === "failed" || old.status === "killed";
|
|
119
144
|
var nowTerminal = snapshot.status === "completed" || snapshot.status === "failed" || snapshot.status === "killed";
|
|
145
|
+
if (snapshot.needsApproval && snapshot.approvalToken && onApprovalToken) {
|
|
146
|
+
try { onApprovalToken(snapshot.spec, snapshot.approvalToken); } catch (e) { /* best effort */ }
|
|
147
|
+
}
|
|
120
148
|
if (!wasTerminal && nowTerminal && onSettled) {
|
|
121
|
-
try { onSettled(id); } catch (e) { /* best effort */ }
|
|
149
|
+
try { onSettled(id, snapshot); } catch (e) { /* best effort */ }
|
|
122
150
|
}
|
|
123
151
|
var output = (old.output || "") + (value.output || "");
|
|
124
152
|
jobsRef.current = Object.assign({}, jobsRef.current, { [id]: Object.assign({}, old, {
|
|
125
153
|
status: snapshot.status,
|
|
126
154
|
detail: snapshot.detail,
|
|
127
155
|
needsApproval: snapshot.needsApproval,
|
|
156
|
+
approvalToken: snapshot.approvalToken,
|
|
128
157
|
output: output,
|
|
129
158
|
}) });
|
|
130
159
|
setJobs(Object.assign({}, jobsRef.current));
|
|
@@ -132,7 +161,7 @@ window.__ModuleLoader__.load({
|
|
|
132
161
|
});
|
|
133
162
|
}, 1200);
|
|
134
163
|
return function () { clearInterval(timer); };
|
|
135
|
-
}, [call, onSettled]);
|
|
164
|
+
}, [call, onSettled, onApprovalToken]);
|
|
136
165
|
var track = useCallback(function (id, spec) {
|
|
137
166
|
jobsRef.current = Object.assign({}, jobsRef.current, { [id]: { status: "running", spec: spec, output: "" } });
|
|
138
167
|
setJobs(Object.assign({}, jobsRef.current));
|
|
@@ -151,8 +180,6 @@ window.__ModuleLoader__.load({
|
|
|
151
180
|
}
|
|
152
181
|
|
|
153
182
|
// ── plugin verification badge ───────────────────────────────────────────
|
|
154
|
-
// verified 来自 /market verify 端点(node 侧拉 raw package.json 判定
|
|
155
|
-
// dsh.bundle/dsh.client 声明,进程内缓存)。unknown 不显示徽章。
|
|
156
183
|
function verifyBadge(verified) {
|
|
157
184
|
if (verified === undefined || verified === null) return null;
|
|
158
185
|
if (verified.hostDeps !== undefined && verified.hostDeps.length > 0) {
|
|
@@ -165,6 +192,19 @@ window.__ModuleLoader__.load({
|
|
|
165
192
|
return null;
|
|
166
193
|
}
|
|
167
194
|
|
|
195
|
+
// ── browsing-time compat badge ──────────────────────────────────────────
|
|
196
|
+
// Static server-side scan of the repo manifest/patch against the profile.
|
|
197
|
+
// Advisory only: the install preflight remains the enforcing gate.
|
|
198
|
+
function compatBadge(compat) {
|
|
199
|
+
if (compat === undefined || compat === null) return null;
|
|
200
|
+
var titles = (compat.issues || []).map(function (item) { return (item.severity === "block" ? "[阻断] " : "[警告] ") + item.title; });
|
|
201
|
+
var tip = (compat.summary || "") + (titles.length > 0 ? "\n" + titles.join("\n") : "") + (compat.patchChecked === false ? "\n补丁未获取,加载冲突未检查" : "");
|
|
202
|
+
if (compat.state === "conflict") return h("span", { className: "mkt_badge mkt_badgeBad", title: tip }, "冲突");
|
|
203
|
+
if (compat.state === "warning") return h("span", { className: "mkt_badge mkt_badgeWarn", title: tip }, "有风险");
|
|
204
|
+
if (compat.state === "compatible") return h("span", { className: "mkt_badge mkt_badgeOk", title: tip }, "适配");
|
|
205
|
+
return h("span", { className: "mkt_badge", title: tip || "兼容性未知" }, "适配未知");
|
|
206
|
+
}
|
|
207
|
+
|
|
168
208
|
// ── repo card ───────────────────────────────────────────────────────────
|
|
169
209
|
function RepoCard(props) {
|
|
170
210
|
var item = props.item;
|
|
@@ -182,6 +222,7 @@ window.__ModuleLoader__.load({
|
|
|
182
222
|
h("div", { className: "mkt_cardHead" },
|
|
183
223
|
h("span", { className: "mkt_name" }, item.fullName),
|
|
184
224
|
verifyBadge(props.verified),
|
|
225
|
+
compatBadge(props.compat),
|
|
185
226
|
item.archived ? h("span", { className: "mkt_badge" }, "archived") : null
|
|
186
227
|
),
|
|
187
228
|
item.description ? h("div", { className: "mkt_desc" }, item.description) : null,
|
|
@@ -200,21 +241,17 @@ window.__ModuleLoader__.load({
|
|
|
200
241
|
}
|
|
201
242
|
|
|
202
243
|
// ── install-script approval ─────────────────────────────────────────────
|
|
203
|
-
// pnpm 默认拦掉依赖的安装期脚本。放行它们 = 让这些命令以用户的权限在用户
|
|
204
|
-
// 机器上运行,而且在任何插件代码加载之前。这个决定属于用户,所以这里如实
|
|
205
|
-
// 摆出「要批准的到底是什么」——包名、版本、确切的命令、以及它究竟是用户
|
|
206
|
-
// 选的那个插件,还是一个他从没听说过的传递依赖。
|
|
207
|
-
// 措辞刻意不写「安全检查」:批准这些脚本,对插件装好之后会做什么一无所证。
|
|
208
244
|
function ApprovalRequest(props) {
|
|
209
245
|
var pkgs = props.needsApproval || [];
|
|
210
246
|
return h("div", { className: "mkt_approve" },
|
|
211
247
|
h("div", { className: "mkt_approveHead" }, "需要你确认:这次安装会执行安装期代码"),
|
|
212
|
-
pkgs.map(function (p) {
|
|
248
|
+
pkgs.map(function (p, index) {
|
|
213
249
|
var facts = [];
|
|
214
250
|
if (typeof p.weeklyDownloads === "number") facts.push("周下载 " + p.weeklyDownloads.toLocaleString());
|
|
215
251
|
facts.push(p.provenance === true ? "有来源证明" : "无来源证明");
|
|
216
252
|
if (typeof p.unpackedSize === "number") facts.push((Math.round(p.unpackedSize / 104857.6) / 10) + " MB");
|
|
217
|
-
|
|
253
|
+
if (p.selector) facts.push("pnpm 标识 " + p.selector);
|
|
254
|
+
return h("div", { key: p.selector || (p.name + ":" + index), className: "mkt_approvePkg" },
|
|
218
255
|
h("div", null,
|
|
219
256
|
h("span", { className: "mkt_approveName" }, p.name + (p.version ? "@" + p.version : "")),
|
|
220
257
|
h("span", { className: "mkt_badge " + (p.direct ? "" : "mkt_badgeBad") },
|
|
@@ -223,6 +260,7 @@ window.__ModuleLoader__.load({
|
|
|
223
260
|
Object.keys(p.scripts || {}).map(function (k) {
|
|
224
261
|
return h("pre", { key: k, className: "mkt_pre mkt_approveCmd" }, k + ": " + p.scripts[k]);
|
|
225
262
|
}),
|
|
263
|
+
p.contentHash ? h("div", { className: "mkt_pre mkt_approveCmd" }, "制品 SHA-256: " + p.contentHash) : null,
|
|
226
264
|
h("div", { className: "mkt_meta" }, facts.join(" · "))
|
|
227
265
|
);
|
|
228
266
|
}),
|
|
@@ -238,6 +276,47 @@ window.__ModuleLoader__.load({
|
|
|
238
276
|
);
|
|
239
277
|
}
|
|
240
278
|
|
|
279
|
+
// ── preflight result card ────────────────────────────────────────────────
|
|
280
|
+
// 预检通过(safe)根本不渲染这张卡片——直接开始安装,不打扰。只有
|
|
281
|
+
// warning / blocked 才出现,且用任务面板同款的内联卡片语言,不弹模态框:
|
|
282
|
+
// 市场里一切任务都在面板里流动,弹窗会打断这个节奏。
|
|
283
|
+
function PreflightCard(props) {
|
|
284
|
+
var report = props.report || {};
|
|
285
|
+
var blocked = report.verdict === "blocked";
|
|
286
|
+
var title = blocked ? "安装被阻止" : "安装存在风险";
|
|
287
|
+
var issues = report.issues || [];
|
|
288
|
+
return h("div", { className: "mkt_card" },
|
|
289
|
+
h("div", { className: "mkt_panelRow" },
|
|
290
|
+
h("p", { className: "mkt_panelTitle" + (blocked ? " mkt_error" : "") }, title),
|
|
291
|
+
h("span", { className: "mkt_meta" }, clip(props.spec, 50))
|
|
292
|
+
),
|
|
293
|
+
report.summary ? h("div", { className: "mkt_desc" }, report.summary) : null,
|
|
294
|
+
issues.length > 0 ? h("ul", { className: "mkt_issueList" },
|
|
295
|
+
issues.map(function (issue) {
|
|
296
|
+
return h("li", {
|
|
297
|
+
key: (issue.code || "") + "-" + (issue.title || ""),
|
|
298
|
+
className: "mkt_issue " + (issue.severity === "block" ? "mkt_issueBlock" : "mkt_issueWarn"),
|
|
299
|
+
},
|
|
300
|
+
h("div", { className: "mkt_issueTitle" }, issue.title),
|
|
301
|
+
issue.detail ? h("div", { className: "mkt_issueDetail" }, issue.detail) : null);
|
|
302
|
+
})
|
|
303
|
+
) : null,
|
|
304
|
+
h("div", { className: "mkt_row" },
|
|
305
|
+
blocked
|
|
306
|
+
? h("button", { className: "mkt_btn mkt_btnSm", onClick: props.onClose }, "关闭")
|
|
307
|
+
: [
|
|
308
|
+
h("button", { key: "cancel", className: "mkt_btn mkt_btnSm", onClick: props.onClose }, "取消"),
|
|
309
|
+
h("button", {
|
|
310
|
+
key: "confirm",
|
|
311
|
+
className: "mkt_btn mkt_btnPrimary mkt_btnSm",
|
|
312
|
+
disabled: props.busy === true,
|
|
313
|
+
onClick: props.onConfirm,
|
|
314
|
+
}, props.busy ? "安装中…" : "我已了解风险,继续安装"),
|
|
315
|
+
]
|
|
316
|
+
)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
241
320
|
// ── jobs panel ──────────────────────────────────────────────────────────
|
|
242
321
|
function JobsPanel(props) {
|
|
243
322
|
var ids = Object.keys(props.jobs);
|
|
@@ -253,13 +332,16 @@ window.__ModuleLoader__.load({
|
|
|
253
332
|
return h("div", { key: id, style: { display: "flex", flexDirection: "column", gap: "4px" } },
|
|
254
333
|
h("div", { className: "mkt_meta" },
|
|
255
334
|
id + " · " + clip(job.spec || "", 40) + " · " + (job.status || "running")),
|
|
256
|
-
// 待批准不是失败:什么都没执行,profile 也没动过。所以不显示
|
|
257
|
-
// 失败文案,直接给出「要批准什么」和两个按钮。
|
|
258
335
|
job.needsApproval && job.needsApproval.length > 0
|
|
259
336
|
? h(ApprovalRequest, {
|
|
260
337
|
needsApproval: job.needsApproval,
|
|
261
338
|
busy: props.approving === job.spec,
|
|
262
|
-
onApprove: function (names) {
|
|
339
|
+
onApprove: function (names) {
|
|
340
|
+
if (typeof props.onDrop === "function") {
|
|
341
|
+
props.onDrop(id);
|
|
342
|
+
}
|
|
343
|
+
props.onApprove(job.spec, names, job.approvalToken);
|
|
344
|
+
},
|
|
263
345
|
onDismiss: function () { props.onDismiss(id); },
|
|
264
346
|
})
|
|
265
347
|
: job.detail ? h("div", { className: "mkt_desc" }, job.detail) : null,
|
|
@@ -311,11 +393,6 @@ window.__ModuleLoader__.load({
|
|
|
311
393
|
h("span", { className: "mkt_desc" }, dep.name + "@" + dep.version),
|
|
312
394
|
h("span", { className: "mkt_depActions" },
|
|
313
395
|
h("span", { className: "mkt_badge" }, kindLabel(dep.kind)),
|
|
314
|
-
// 必须带上版本号。传裸包名等于让 pnpm 自己挑"最新可安装
|
|
315
|
-
// 版本",而 pnpm 11 的 minimumReleaseAge 会拒绝发布不足
|
|
316
|
-
// 24 小时的版本并回落到旧版 —— 按钮写着"更新至 0.12.3",
|
|
317
|
-
// pnpm 却报 "Already up to date",点多少次都不动。
|
|
318
|
-
// 带上版本是明确指定,pnpm 照装并自行记一条豁免。
|
|
319
396
|
upd && upd.hasUpdate ? h("button", {
|
|
320
397
|
className: "mkt_btn mkt_btnSm",
|
|
321
398
|
onClick: function () { props.onInstallSpec(dep.name + "@" + upd.latest); },
|
|
@@ -363,9 +440,13 @@ window.__ModuleLoader__.load({
|
|
|
363
440
|
var loadingMore = _loadingMore[0];
|
|
364
441
|
var setLoadingMore = _loadingMore[1];
|
|
365
442
|
var sentinelRef = useRef(null);
|
|
443
|
+
var resultsRef = useRef(null);
|
|
366
444
|
var _verified = useState({});
|
|
367
445
|
var verified = _verified[0];
|
|
368
446
|
var setVerified = _verified[1];
|
|
447
|
+
var _compat = useState({});
|
|
448
|
+
var compat = _compat[0];
|
|
449
|
+
var setCompat = _compat[1];
|
|
369
450
|
var _verifiedOnly = useState(true);
|
|
370
451
|
var verifiedOnly = _verifiedOnly[0];
|
|
371
452
|
var setVerifiedOnly = _verifiedOnly[1];
|
|
@@ -381,9 +462,19 @@ window.__ModuleLoader__.load({
|
|
|
381
462
|
var _restarting = useState(false);
|
|
382
463
|
var restarting = _restarting[0];
|
|
383
464
|
var setRestarting = _restarting[1];
|
|
465
|
+
var _preflight = useState(null);
|
|
466
|
+
var preflight = _preflight[0];
|
|
467
|
+
var setPreflight = _preflight[1];
|
|
468
|
+
|
|
469
|
+
// Opaque one-shot approval tokens issued from needsApproval outcomes (spec -> token).
|
|
470
|
+
// Cleared on success, unrelated failure, cancel, modal close, or spec change.
|
|
471
|
+
// No boolean warning-consent map is kept.
|
|
472
|
+
var sessionNonce = useRef(generateSessionNonce()).current;
|
|
473
|
+
var approvalTokensRef = useRef({});
|
|
384
474
|
|
|
385
475
|
var call = useCallback(function (endpoint, payload) {
|
|
386
|
-
|
|
476
|
+
var body = Object.assign({ session: sessionNonce }, payload || {});
|
|
477
|
+
return rpc.call("/market", endpoint, body).then(function (res) {
|
|
387
478
|
if (!res || res.ok !== true) {
|
|
388
479
|
var err = res && res.error;
|
|
389
480
|
var msg = err && typeof err === "object" ? (err.message || JSON.stringify(err)) : (err || "market request failed");
|
|
@@ -391,16 +482,17 @@ window.__ModuleLoader__.load({
|
|
|
391
482
|
}
|
|
392
483
|
return res.value;
|
|
393
484
|
});
|
|
394
|
-
}, [rpc]);
|
|
485
|
+
}, [rpc, sessionNonce]);
|
|
395
486
|
|
|
396
|
-
// 每页搜索结果到货后批量验证(node 侧 raw CDN,带缓存),失败静默——
|
|
397
|
-
// 徽章缺失可接受,不该打断浏览。
|
|
398
487
|
var verifyPage = useCallback(function (items) {
|
|
399
488
|
var repos = (items || []).map(function (it) { return it.fullName; });
|
|
400
489
|
if (repos.length === 0) return;
|
|
401
490
|
call("verify", { repos: repos }).then(function (value) {
|
|
402
491
|
setVerified(function (prev) { return Object.assign({}, prev, value.results); });
|
|
403
|
-
}).catch(function () { /*
|
|
492
|
+
}).catch(function () { /* optional */ });
|
|
493
|
+
call("compat", { repos: repos }).then(function (value) {
|
|
494
|
+
setCompat(function (prev) { return Object.assign({}, prev, value.results); });
|
|
495
|
+
}).catch(function () { /* optional */ });
|
|
404
496
|
}, [call]);
|
|
405
497
|
|
|
406
498
|
var doSearch = useCallback(function () {
|
|
@@ -412,6 +504,7 @@ window.__ModuleLoader__.load({
|
|
|
412
504
|
call("search", { query: query, sort: sort, perPage: 20 }).then(function (value) {
|
|
413
505
|
setResults(value);
|
|
414
506
|
setVerified({});
|
|
507
|
+
setCompat({});
|
|
415
508
|
verifyPage(value.items);
|
|
416
509
|
}).catch(function (e) {
|
|
417
510
|
setError(errorText(e));
|
|
@@ -420,17 +513,11 @@ window.__ModuleLoader__.load({
|
|
|
420
513
|
});
|
|
421
514
|
}, [call, query, sort, verifyPage]);
|
|
422
515
|
|
|
423
|
-
// 无限滚动:哨兵进入视口时拉下一页并追加。GitHub topic 的翻页间数据
|
|
424
|
-
// 可能移动造成重复,按 fullName 去重;已显示数追上 total 即到底。
|
|
425
516
|
var canLoadMore = results !== null && results.items.length < results.total && !reachedLimit;
|
|
426
517
|
var loadMoreLock = useRef(false);
|
|
427
518
|
var loadMore = useCallback(function () {
|
|
428
519
|
if (!canLoadMore || loading || loadingMore) return;
|
|
429
|
-
// 同一 tick 内 observer 可能连续触发两次,state 更新不阻塞闭包读值,
|
|
430
|
-
// 用 ref 做重入闸。
|
|
431
520
|
if (loadMoreLock.current) return;
|
|
432
|
-
// 限流熔断:失败后 60s 内不再自动请求(哨兵重渲染会反复触发
|
|
433
|
-
// IntersectionObserver,不熔断会连环 500 直到限流窗口过去)。
|
|
434
521
|
if (Date.now() < retryAt) return;
|
|
435
522
|
loadMoreLock.current = true;
|
|
436
523
|
setLoadingMore(true);
|
|
@@ -456,7 +543,7 @@ window.__ModuleLoader__.load({
|
|
|
456
543
|
loadMoreLock.current = false;
|
|
457
544
|
setLoadingMore(false);
|
|
458
545
|
});
|
|
459
|
-
}, [call, canLoadMore, loading, loadingMore, page, query, sort, retryAt]);
|
|
546
|
+
}, [call, canLoadMore, loading, loadingMore, page, query, sort, retryAt, verifyPage]);
|
|
460
547
|
|
|
461
548
|
useEffect(function () {
|
|
462
549
|
var node = sentinelRef.current;
|
|
@@ -480,8 +567,24 @@ window.__ModuleLoader__.load({
|
|
|
480
567
|
});
|
|
481
568
|
}, [call]);
|
|
482
569
|
|
|
483
|
-
|
|
484
|
-
|
|
570
|
+
var onJobSettled = useCallback(function (id, snapshot) {
|
|
571
|
+
refreshInstalled();
|
|
572
|
+
// 装/卸改变了 profile,旧适配徽标全部失效,按当前列表重扫。
|
|
573
|
+
setCompat({});
|
|
574
|
+
var items = resultsRef.current ? resultsRef.current.items : [];
|
|
575
|
+
if (items.length > 0) verifyPage(items);
|
|
576
|
+
if (snapshot && (snapshot.status === "completed" || (snapshot.status === "failed" && !snapshot.needsApproval) || snapshot.status === "killed")) {
|
|
577
|
+
if (snapshot.spec) delete approvalTokensRef.current[snapshot.spec];
|
|
578
|
+
}
|
|
579
|
+
}, [refreshInstalled, verifyPage]);
|
|
580
|
+
|
|
581
|
+
var onApprovalToken = useCallback(function (spec, token) {
|
|
582
|
+
if (spec && token) {
|
|
583
|
+
approvalTokensRef.current[spec] = token;
|
|
584
|
+
}
|
|
585
|
+
}, []);
|
|
586
|
+
|
|
587
|
+
var polling = useJobPolling(call, onJobSettled, onApprovalToken);
|
|
485
588
|
var track = polling.track;
|
|
486
589
|
var jobs = polling.jobs;
|
|
487
590
|
var clearJobs = polling.clear;
|
|
@@ -492,20 +595,17 @@ window.__ModuleLoader__.load({
|
|
|
492
595
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
493
596
|
}, []);
|
|
494
597
|
|
|
495
|
-
|
|
496
|
-
// (已装面板的更新按钮)。后端会把同源发布的 github spec 改写为 npm
|
|
497
|
-
// tarball 安装(更快),job 里记录的是最终 spec。
|
|
498
|
-
var doInstallSpec = useCallback(function (spec, allowBuildScripts) {
|
|
598
|
+
var doRawInstall = useCallback(function (spec, extra) {
|
|
499
599
|
setInstalling(function (prev) {
|
|
500
600
|
var next = Object.assign({}, prev, { [spec]: true });
|
|
501
601
|
return next;
|
|
502
602
|
});
|
|
503
603
|
setError(null);
|
|
504
|
-
var payload = { spec: spec };
|
|
505
|
-
if (allowBuildScripts) payload.allowBuildScripts = allowBuildScripts;
|
|
604
|
+
var payload = Object.assign({ spec: spec }, extra || {});
|
|
506
605
|
call("install", payload).then(function (value) {
|
|
507
606
|
track(value.jobId, spec);
|
|
508
607
|
}).catch(function (e) {
|
|
608
|
+
delete approvalTokensRef.current[spec];
|
|
509
609
|
setError(errorText(e));
|
|
510
610
|
}).finally(function () {
|
|
511
611
|
setInstalling(function (prev) {
|
|
@@ -516,18 +616,46 @@ window.__ModuleLoader__.load({
|
|
|
516
616
|
});
|
|
517
617
|
}, [call, track]);
|
|
518
618
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
619
|
+
var doApprove = useCallback(function (spec, names, token) {
|
|
620
|
+
var extra = { allowBuildScripts: names };
|
|
621
|
+
var apprToken = token || approvalTokensRef.current[spec];
|
|
622
|
+
if (apprToken) {
|
|
623
|
+
extra.approvalToken = apprToken;
|
|
624
|
+
delete approvalTokensRef.current[spec];
|
|
625
|
+
}
|
|
626
|
+
doRawInstall(spec, extra);
|
|
627
|
+
}, [doRawInstall]);
|
|
628
|
+
|
|
629
|
+
var preflightAndInstall = useCallback(function (spec) {
|
|
630
|
+
delete approvalTokensRef.current[spec];
|
|
631
|
+
setInstalling(function (prev) {
|
|
632
|
+
var next = Object.assign({}, prev, { [spec]: true });
|
|
633
|
+
return next;
|
|
634
|
+
});
|
|
635
|
+
setError(null);
|
|
636
|
+
call("preflight", { spec: spec }).then(function (report) {
|
|
637
|
+
// 检查通过直接装,不打扰;有风险才出内联卡片等人表态;被阻止
|
|
638
|
+
// 只告知原因。任何路径都不再弹模态框。
|
|
639
|
+
if (report.verdict === "safe") {
|
|
640
|
+
doRawInstall(spec, {});
|
|
641
|
+
} else {
|
|
642
|
+
setPreflight({ spec: spec, report: report });
|
|
643
|
+
}
|
|
644
|
+
}).catch(function (e) {
|
|
645
|
+
setError(errorText(e));
|
|
646
|
+
}).finally(function () {
|
|
647
|
+
setInstalling(function (prev) {
|
|
648
|
+
var next = Object.assign({}, prev);
|
|
649
|
+
delete next[spec];
|
|
650
|
+
return next;
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
}, [call, doRawInstall]);
|
|
524
654
|
|
|
525
655
|
var doInstall = useCallback(function (repo) {
|
|
526
|
-
|
|
527
|
-
}, [
|
|
656
|
+
preflightAndInstall("github:" + repo);
|
|
657
|
+
}, [preflightAndInstall]);
|
|
528
658
|
|
|
529
|
-
// 一键重启:loopback-only 的 /market restart 端点会 detached 拉起新的
|
|
530
|
-
// dsh 进程后退出当前进程;这里轮询 host 恢复后自动刷新页面。
|
|
531
659
|
var doRestart = useCallback(function () {
|
|
532
660
|
if (typeof window !== "undefined" && typeof window.confirm === "function") {
|
|
533
661
|
if (window.confirm("重启 dsh?正在进行的任务会中断。") !== true) return;
|
|
@@ -547,7 +675,7 @@ window.__ModuleLoader__.load({
|
|
|
547
675
|
rpc.call("/market", "installed", {}).then(function () {
|
|
548
676
|
clearInterval(ping);
|
|
549
677
|
window.location.reload();
|
|
550
|
-
}).catch(function () { /* host
|
|
678
|
+
}).catch(function () { /* host restarting */ });
|
|
551
679
|
}, 3000);
|
|
552
680
|
}).catch(function (e) {
|
|
553
681
|
setRestarting(false);
|
|
@@ -556,6 +684,7 @@ window.__ModuleLoader__.load({
|
|
|
556
684
|
}, [call, rpc]);
|
|
557
685
|
|
|
558
686
|
var doUninstall = useCallback(function (name) {
|
|
687
|
+
delete approvalTokensRef.current[name];
|
|
559
688
|
setRemoving(function (prev) {
|
|
560
689
|
var next = Object.assign({}, prev, { [name]: true });
|
|
561
690
|
return next;
|
|
@@ -578,16 +707,13 @@ window.__ModuleLoader__.load({
|
|
|
578
707
|
refreshInstalled();
|
|
579
708
|
}, [refreshInstalled]);
|
|
580
709
|
|
|
581
|
-
// 任务面板位置固定(已装面板之后):不随任务状态在顶部/底部之间
|
|
582
|
-
// 跳——npm 安装几秒即完成,"完成即落底"曾让结果凭空消失。
|
|
583
|
-
// "只看已验证"开关下的可见项:verify 判定 bundle/client 的才算插件。
|
|
584
|
-
// verifyPending:当前加载的仓库里还有未验证完的(verified map 尚未覆盖)。
|
|
585
710
|
var visibleItems = results === null ? [] : results.items.filter(function (it) {
|
|
586
711
|
if (verifiedOnly !== true) return true;
|
|
587
712
|
var v = verified[it.fullName];
|
|
588
713
|
return v !== undefined && (v.kind === "bundle" || v.kind === "client")
|
|
589
714
|
&& !(v.hostDeps !== undefined && v.hostDeps.length > 0);
|
|
590
715
|
});
|
|
716
|
+
resultsRef.current = results;
|
|
591
717
|
var verifyPending = results !== null && results.items.some(function (it) { return verified[it.fullName] === undefined; });
|
|
592
718
|
|
|
593
719
|
return h("div", { className: "mkt_root" },
|
|
@@ -620,16 +746,45 @@ window.__ModuleLoader__.load({
|
|
|
620
746
|
"只看已验证插件")
|
|
621
747
|
),
|
|
622
748
|
error ? h("div", { className: "mkt_error" }, error) : null,
|
|
623
|
-
h(InstalledPanel, { installed: installed, removing: removing, updates: updates, onUninstall: doUninstall, onInstallSpec:
|
|
749
|
+
h(InstalledPanel, { installed: installed, removing: removing, updates: updates, onUninstall: doUninstall, onInstallSpec: preflightAndInstall }),
|
|
624
750
|
h(JobsPanel, {
|
|
625
751
|
jobs: jobs,
|
|
626
|
-
onClear:
|
|
752
|
+
onClear: function () {
|
|
753
|
+
Object.keys(jobs).forEach(function (id) {
|
|
754
|
+
var job = jobs[id];
|
|
755
|
+
var token = job && job.approvalToken ? job.approvalToken : (job && job.spec ? approvalTokensRef.current[job.spec] : undefined);
|
|
756
|
+
call("jobDismiss", { jobId: id, token: token }).catch(function () {});
|
|
757
|
+
});
|
|
758
|
+
approvalTokensRef.current = {};
|
|
759
|
+
clearJobs();
|
|
760
|
+
},
|
|
627
761
|
onApprove: doApprove,
|
|
628
|
-
onDismiss:
|
|
762
|
+
onDismiss: function (id) {
|
|
763
|
+
var job = jobs[id];
|
|
764
|
+
var token = job && job.approvalToken ? job.approvalToken : (job && job.spec ? approvalTokensRef.current[job.spec] : undefined);
|
|
765
|
+
if (job && job.spec) delete approvalTokensRef.current[job.spec];
|
|
766
|
+
call("jobDismiss", { jobId: id, token: token }).catch(function () {});
|
|
767
|
+
dropJob(id);
|
|
768
|
+
},
|
|
769
|
+
onDrop: dropJob,
|
|
629
770
|
onRestart: doRestart,
|
|
630
771
|
restarting: restarting,
|
|
631
772
|
approving: Object.keys(installing).filter(function (s) { return installing[s]; })[0],
|
|
632
773
|
}),
|
|
774
|
+
preflight ? h(PreflightCard, {
|
|
775
|
+
spec: preflight.spec,
|
|
776
|
+
report: preflight.report,
|
|
777
|
+
busy: installing[preflight.spec] === true,
|
|
778
|
+
onConfirm: function () {
|
|
779
|
+
var spec = preflight.spec;
|
|
780
|
+
setPreflight(null);
|
|
781
|
+
doRawInstall(spec, { acceptWarnings: true });
|
|
782
|
+
},
|
|
783
|
+
onClose: function () {
|
|
784
|
+
if (preflight && preflight.spec) delete approvalTokensRef.current[preflight.spec];
|
|
785
|
+
setPreflight(null);
|
|
786
|
+
},
|
|
787
|
+
}) : null,
|
|
633
788
|
h("div", { className: "mkt_list" },
|
|
634
789
|
results == null
|
|
635
790
|
? h("div", { className: "mkt_meta mkt_listHead" }, loading ? "正在加载最热插件…" : "—")
|
|
@@ -657,6 +812,7 @@ window.__ModuleLoader__.load({
|
|
|
657
812
|
installing: installing[item.fullName] === true || installing["github:" + item.fullName] === true,
|
|
658
813
|
installJob: installJob,
|
|
659
814
|
verified: vInfo,
|
|
815
|
+
compat: compat[item.fullName],
|
|
660
816
|
alreadyInstalled: alreadyInstalled,
|
|
661
817
|
onInstall: doInstall,
|
|
662
818
|
});
|