dsh-m 0.3.0 → 0.4.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.en.md +19 -6
- package/README.md +19 -6
- package/docs/DESIGN.md +11 -1
- package/lib/cli.js +41 -8
- package/lib/client.js +317 -15
- package/lib/core/build-approval.js +88 -0
- package/lib/core/compat-check.js +56 -0
- package/lib/core/dsh-cli.js +34 -7
- package/lib/core/enablement.js +92 -0
- package/lib/core/host-api.js +74 -6
- package/lib/core/installed.js +9 -0
- package/lib/core/market.js +100 -8
- package/lib/core/patch-yaml.js +143 -0
- package/lib/core/profile-transaction.js +30 -2
- package/lib/core/registry-probe.js +82 -0
- package/lib/core/registry.js +32 -1
- package/lib/core/settings-compat.js +14 -0
- package/lib/core/toggle.js +220 -0
- package/lib/core/versions.js +18 -5
- package/lib/host.js +53 -2
- package/lib/tools.js +69 -5
- package/package.json +7 -1
- package/registry.json +128 -22
package/lib/client.js
CHANGED
|
@@ -306,7 +306,10 @@ function installedViewModel(it) {
|
|
|
306
306
|
sourceLabelKey: { npm: "src.npm", github: "src.github", link: "src.link", file: "src.file", unknown: "src.unknown" }[it.source] || null,
|
|
307
307
|
guard: uninstallGuardKeys(it),
|
|
308
308
|
latestLabel: it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : ""),
|
|
309
|
-
latestLabelDetail: it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : "\u2014")
|
|
309
|
+
latestLabelDetail: it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : "\u2014"),
|
|
310
|
+
// 0.4.0(Task 17):运行相位/开关状态的 sub 行渲染原料;相位点 class 映射归 toggle-view
|
|
311
|
+
phaseKey: it.phase ? `phase.${it.phase}` : null,
|
|
312
|
+
enabledLabelKey: it.enabled === false ? "toggle.state.off" : "toggle.state.on"
|
|
310
313
|
};
|
|
311
314
|
}
|
|
312
315
|
function registrySourceKey(data) {
|
|
@@ -334,6 +337,51 @@ var init_installed_view = __esm({
|
|
|
334
337
|
}
|
|
335
338
|
});
|
|
336
339
|
|
|
340
|
+
// src/client/toggle-view.js
|
|
341
|
+
var toggle_view_exports = {};
|
|
342
|
+
__export(toggle_view_exports, {
|
|
343
|
+
toggleNoticeKeys: () => toggleNoticeKeys,
|
|
344
|
+
toggleViewModel: () => toggleViewModel
|
|
345
|
+
});
|
|
346
|
+
function toggleViewModel(it) {
|
|
347
|
+
const phase = PHASE_VIEW[it.phase] ?? { phaseDotClass: "", phaseLabelKey: null };
|
|
348
|
+
const toggleable = it.toggleable !== false && it.lockReason === void 0;
|
|
349
|
+
return {
|
|
350
|
+
phaseDotClass: phase.phaseDotClass,
|
|
351
|
+
phaseLabelKey: phase.phaseLabelKey,
|
|
352
|
+
switchOn: it.enabled === true,
|
|
353
|
+
switchDisabled: !toggleable,
|
|
354
|
+
switchTitleKey: it.lockReason ? LOCK_TITLE_KEYS[it.lockReason] || null : null
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function toggleNoticeKeys(result) {
|
|
358
|
+
const direction = result.enabled === true ? "on" : "off";
|
|
359
|
+
const applied = result.applied === "live" ? "live" : "restart";
|
|
360
|
+
return {
|
|
361
|
+
textKey: `notify.toggled.${direction}.${applied}`,
|
|
362
|
+
params: { pkg: result.pkg || "" },
|
|
363
|
+
needsRestart: applied === "restart"
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
var PHASE_VIEW, LOCK_TITLE_KEYS;
|
|
367
|
+
var init_toggle_view = __esm({
|
|
368
|
+
"src/client/toggle-view.js"() {
|
|
369
|
+
"use strict";
|
|
370
|
+
PHASE_VIEW = {
|
|
371
|
+
active: { phaseDotClass: "ok", phaseLabelKey: "phase.active" },
|
|
372
|
+
failed: { phaseDotClass: "err", phaseLabelKey: "phase.failed" },
|
|
373
|
+
pending: { phaseDotClass: "idle", phaseLabelKey: "phase.pending" },
|
|
374
|
+
loading: { phaseDotClass: "busy", phaseLabelKey: "phase.loading" },
|
|
375
|
+
unloading: { phaseDotClass: "busy", phaseLabelKey: "phase.unloading" }
|
|
376
|
+
};
|
|
377
|
+
LOCK_TITLE_KEYS = {
|
|
378
|
+
self: "toggle.lock.self",
|
|
379
|
+
protected: "toggle.lock.protected",
|
|
380
|
+
"no-entry": "toggle.lock.noentry"
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
337
385
|
// src/client/tool-view.js
|
|
338
386
|
var tool_view_exports = {};
|
|
339
387
|
__export(tool_view_exports, {
|
|
@@ -438,6 +486,7 @@ var { MARKET_PAGE_SIZE: MARKET_PAGE_SIZE2, normalizeMarketQuery: normalizeMarket
|
|
|
438
486
|
var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
|
|
439
487
|
var { ExtLink, MdImg, renderMarkdown } = createMarkdown2(h);
|
|
440
488
|
var { installedViewModel: installedViewModel2, registrySourceKey: registrySourceKey2 } = (init_installed_view(), __toCommonJS(installed_view_exports));
|
|
489
|
+
var { toggleViewModel: toggleViewModel2, toggleNoticeKeys: toggleNoticeKeys2 } = (init_toggle_view(), __toCommonJS(toggle_view_exports));
|
|
441
490
|
var { pickPayload: pickPayload2, parseToolArgs: parseToolArgs2 } = (init_tool_view(), __toCommonJS(tool_view_exports));
|
|
442
491
|
var { RESTART_POLL_MS: RESTART_POLL_MS2, RESTART_DEADLINE_MS: RESTART_DEADLINE_MS2, nextRestartWait: nextRestartWait2, isAmbiguousRestartRequestError: isAmbiguousRestartRequestError2 } = (init_restart_wait(), __toCommonJS(restart_wait_exports));
|
|
443
492
|
var { refreshAfterMutation: refreshAfterMutation2 } = (init_view_refresh(), __toCommonJS(view_refresh_exports));
|
|
@@ -572,6 +621,37 @@ var ZH = {
|
|
|
572
621
|
"registry.refreshed": "\u6536\u5F55\u6E05\u5355\u5DF2\u5F3A\u5236\u5237\u65B0",
|
|
573
622
|
"notify.installed": "\u5DF2\u5B89\u88C5 {pkg}{version}",
|
|
574
623
|
"notify.allowbuilds": "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u63D2\u4EF6\u6267\u884C\u4E86\u6784\u5EFA\u811A\u672C\uFF0C\u5DF2\u6309\u7B56\u7565\u653E\u884C\uFF09",
|
|
624
|
+
"notify.builds": "\uFF08\u5DF2\u7CBE\u786E\u653E\u884C\u6784\u5EFA\u811A\u672C\uFF1A{names}\uFF09",
|
|
625
|
+
"notify.builds.fallback": "\uFF08\u6CE8\u610F\uFF1A\u6784\u5EFA\u811A\u672C\u540D\u5355\u4E0D\u53EF\u8BFB\uFF0C\u5DF2\u5168\u91CF\u515C\u5E95\u653E\u884C\uFF09",
|
|
626
|
+
"notify.bundlewarning": "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u5305\u65E0\u8865\u4E01\u5C42\uFF0C\u5DF2\u88C5\u5165\u4E3A\u7EAF\u4F9D\u8D56\u4E0D\u4F1A\u751F\u6548\uFF1B\u53EF\u5378\u8F7D\u6216\u5230\u6536\u5F55\u4ED3\u5E93\u53CD\u9988\uFF09",
|
|
627
|
+
"notify.toggled.on.live": "\u5DF2\u542F\u7528 {pkg}\uFF08\u5373\u65F6\u751F\u6548\uFF09",
|
|
628
|
+
"notify.toggled.off.live": "\u5DF2\u505C\u7528 {pkg}\uFF08\u5373\u65F6\u751F\u6548\uFF09",
|
|
629
|
+
"notify.toggled.on.restart": "\u5DF2\u542F\u7528 {pkg}\uFF08\u9700\u91CD\u542F\u751F\u6548\uFF09",
|
|
630
|
+
"notify.toggled.off.restart": "\u5DF2\u505C\u7528 {pkg}\uFF08\u9700\u91CD\u542F\u751F\u6548\uFF09",
|
|
631
|
+
"toggle.state.on": "\u8FD0\u884C\u4E2D",
|
|
632
|
+
"toggle.state.off": "\u5DF2\u505C\u7528",
|
|
633
|
+
"toggle.lock.self": "dsh-m \u81EA\u8EAB\u4E0D\u53EF\u5F00\u5173",
|
|
634
|
+
"toggle.lock.protected": "\u5B98\u65B9\u5BBF\u4E3B\u547D\u8109\uFF0C\u4E0D\u53EF\u5F00\u5173",
|
|
635
|
+
"toggle.lock.noentry": "\u672A\u88C5\u8F7D\uFF08\u4E0D\u5728\u52A0\u8F7D\u6811\uFF09",
|
|
636
|
+
"toggle.failed": "\u5F00\u5173\u64CD\u4F5C\u5931\u8D25\uFF1A{err}",
|
|
637
|
+
"phase.active": "active",
|
|
638
|
+
"phase.failed": "failed",
|
|
639
|
+
"phase.pending": "pending",
|
|
640
|
+
"phase.loading": "loading",
|
|
641
|
+
"phase.unloading": "unloading",
|
|
642
|
+
"compat.title": "\u517C\u5BB9\u6027\u98CE\u9669\u786E\u8BA4",
|
|
643
|
+
"compat.body": "{pkg}@{version} \u58F0\u660E\u7684 peerDependencies \u4E0E\u5F53\u524D DSH {runtime} \u4E0D\u517C\u5BB9\uFF1A",
|
|
644
|
+
"compat.risk": "\u7EE7\u7EED\u5B89\u88C5\u53EF\u80FD\u5BFC\u81F4\u5D29\u6E83\u6216\u6570\u636E\u4E22\u5931\u3002\u786E\u5B9A\u4ECD\u8981\u5B89\u88C5\u5417\uFF1F",
|
|
645
|
+
"compat.force": "\u4ECD\u8981\u5B89\u88C5",
|
|
646
|
+
"common.cancel": "\u53D6\u6D88",
|
|
647
|
+
"settings.probe": "\u5143\u6570\u636E\u6E90",
|
|
648
|
+
"settings.probe.npmjs": "npmjs\uFF08\u7ADE\u901F\u80DC\u51FA\uFF09",
|
|
649
|
+
"settings.probe.mirror": "npmmirror\uFF08\u7ADE\u901F\u80DC\u51FA\uFF09",
|
|
650
|
+
"settings.probe.none": "\u9ED8\u8BA4 npmjs\uFF08\u672A\u63A2\u6D4B/\u53CC\u4E0D\u901A\uFF09",
|
|
651
|
+
"settings.probe.toggle": "\u7ADE\u901F\u63A2\u6D4B",
|
|
652
|
+
"settings.probe.apply": "\u5E94\u7528",
|
|
653
|
+
"settings.probe.applied": "\u63A2\u6D4B\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF08\u5373\u65F6\u751F\u6548\uFF09",
|
|
654
|
+
"settings.probe.failed": "\u63A2\u6D4B\u914D\u7F6E\u4FDD\u5B58\u5931\u8D25\uFF1A{err}",
|
|
575
655
|
"notify.uninstalled": "\u5DF2\u5378\u8F7D {pkg}",
|
|
576
656
|
"notify.livedisabled": "\uFF08\u5DF2\u5148\u4E0B\u7EBF\u8FD0\u884C\u4E2D\u7684\u754C\u9762\uFF09",
|
|
577
657
|
"notify.leftovers": "\uFF1B\u68C0\u6D4B\u5230\u7591\u4F3C\u6B8B\u7559\u6570\u636E\uFF1A{paths}",
|
|
@@ -740,6 +820,37 @@ var EN = {
|
|
|
740
820
|
"registry.refreshed": "Registry force-refreshed",
|
|
741
821
|
"notify.installed": "Installed {pkg}{version}",
|
|
742
822
|
"notify.allowbuilds": " (note: this plugin ran build scripts, allowed by policy)",
|
|
823
|
+
"notify.builds": " (build scripts precisely allowed: {names})",
|
|
824
|
+
"notify.builds.fallback": " (note: pending list unreadable; all builds allowed as fallback)",
|
|
825
|
+
"notify.bundlewarning": " (note: no patch layer \u2014 installed as a plain dependency; uninstall or report to the listing repo)",
|
|
826
|
+
"notify.toggled.on.live": "Enabled {pkg} (applied live)",
|
|
827
|
+
"notify.toggled.off.live": "Disabled {pkg} (applied live)",
|
|
828
|
+
"notify.toggled.on.restart": "Enabled {pkg} (restart required)",
|
|
829
|
+
"notify.toggled.off.restart": "Disabled {pkg} (restart required)",
|
|
830
|
+
"toggle.state.on": "running",
|
|
831
|
+
"toggle.state.off": "disabled",
|
|
832
|
+
"toggle.lock.self": "dsh-m itself cannot be toggled",
|
|
833
|
+
"toggle.lock.protected": "host lifeline module; not toggleable",
|
|
834
|
+
"toggle.lock.noentry": "not composed (absent from loader tree)",
|
|
835
|
+
"toggle.failed": "Toggle failed: {err}",
|
|
836
|
+
"phase.active": "active",
|
|
837
|
+
"phase.failed": "failed",
|
|
838
|
+
"phase.pending": "pending",
|
|
839
|
+
"phase.loading": "loading",
|
|
840
|
+
"phase.unloading": "unloading",
|
|
841
|
+
"compat.title": "Compatibility risk",
|
|
842
|
+
"compat.body": "{pkg}@{version} declares peerDependencies incompatible with DSH {runtime}:",
|
|
843
|
+
"compat.risk": "Proceeding may cause crashes or data loss. Install anyway?",
|
|
844
|
+
"compat.force": "Install anyway",
|
|
845
|
+
"common.cancel": "Cancel",
|
|
846
|
+
"settings.probe": "Metadata source",
|
|
847
|
+
"settings.probe.npmjs": "npmjs (probe winner)",
|
|
848
|
+
"settings.probe.mirror": "npmmirror (probe winner)",
|
|
849
|
+
"settings.probe.none": "default npmjs (not probed/both down)",
|
|
850
|
+
"settings.probe.toggle": "Probe",
|
|
851
|
+
"settings.probe.apply": "Apply",
|
|
852
|
+
"settings.probe.applied": "Probe settings updated (live)",
|
|
853
|
+
"settings.probe.failed": "Failed to save probe settings: {err}",
|
|
743
854
|
"notify.uninstalled": "Uninstalled {pkg}",
|
|
744
855
|
"notify.livedisabled": " (live UI disabled first)",
|
|
745
856
|
"notify.leftovers": "; possible leftover data: {paths}",
|
|
@@ -897,6 +1008,26 @@ var CSS = `
|
|
|
897
1008
|
.dshm-dshchip:hover .dshm-dshchip-tip,.dshm-dshchip:focus-visible .dshm-dshchip-tip{visibility:visible;opacity:1}
|
|
898
1009
|
.dshm-dshchip-tiprow{font:12px/16px ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}
|
|
899
1010
|
.dshm-dshchip-tipsub{display:block;margin-top:2px;font-size:10px;line-height:14px;color:var(--dsw-alias-label-caption,#9ca3af)}
|
|
1011
|
+
|
|
1012
|
+
/* 0.4.0\uFF1A\u5F00\u5173 / \u76F8\u4F4D\u70B9 / \u517C\u5BB9\u786E\u8BA4\u5F39\u7A97 */
|
|
1013
|
+
.dshm-dot{font-size:9px;line-height:1;vertical-align:middle;margin-right:2px}
|
|
1014
|
+
.dshm-dot.ok{color:#22c55e}
|
|
1015
|
+
.dshm-dot.err{color:#ef4444}
|
|
1016
|
+
.dshm-dot.idle{color:#9ca3af}
|
|
1017
|
+
.dshm-dot.busy{color:#f59e0b}
|
|
1018
|
+
.dshm-switch{position:relative;display:inline-flex;align-items:center;width:34px;height:19px;border-radius:10px;background:var(--dsw-alias-fill-secondary,#d1d5db);cursor:pointer;transition:background .15s;flex:none;margin-left:auto}
|
|
1019
|
+
.dshm-switch input{display:none}
|
|
1020
|
+
.dshm-switch.on{background:#22c55e}
|
|
1021
|
+
.dshm-switch.locked{opacity:.45;cursor:not-allowed}
|
|
1022
|
+
.dshm-switch-slider{position:absolute;left:2px;top:2px;width:15px;height:15px;border-radius:50%;background:#fff;transition:left .15s;box-shadow:0 1px 2px rgba(0,0,0,.25)}
|
|
1023
|
+
.dshm-switch.on .dshm-switch-slider{left:17px}
|
|
1024
|
+
.dshm-compat-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:60}
|
|
1025
|
+
.dshm-compat-dialog{max-width:420px;width:calc(100% - 32px);background:var(--dsw-alias-bg-layer-2,#1c2230);border:1px solid var(--dsw-alias-border-l2,#333b4d);border-radius:12px;padding:16px}
|
|
1026
|
+
.dshm-compat-title{font-weight:600;margin-bottom:8px;color:#f87171}
|
|
1027
|
+
.dshm-compat-body{font-size:12px;margin-bottom:6px}
|
|
1028
|
+
.dshm-compat-peers{margin:0 0 8px;padding-left:18px;font-size:12px}
|
|
1029
|
+
.dshm-compat-peers li{font-family:ui-monospace,monospace}
|
|
1030
|
+
.dshm-compat-risk{font-size:12px;color:#f87171;margin-bottom:12px}
|
|
900
1031
|
`;
|
|
901
1032
|
function ensureCss() {
|
|
902
1033
|
if (typeof document === "undefined" || document.getElementById("dshm-css")) return;
|
|
@@ -913,7 +1044,11 @@ async function api(method, params, signal) {
|
|
|
913
1044
|
...signal ? { signal } : {}
|
|
914
1045
|
});
|
|
915
1046
|
const data = await res.json().catch(() => ({}));
|
|
916
|
-
if (!res.ok || data.ok === false)
|
|
1047
|
+
if (!res.ok || data.ok === false) {
|
|
1048
|
+
const err = new Error(data.error || `API ${res.status}`);
|
|
1049
|
+
if (data && typeof data === "object" && data.issue) err.issue = data.issue;
|
|
1050
|
+
throw err;
|
|
1051
|
+
}
|
|
917
1052
|
return data;
|
|
918
1053
|
}
|
|
919
1054
|
function fmtDate(iso) {
|
|
@@ -1012,6 +1147,29 @@ function LinksRow(props) {
|
|
|
1012
1147
|
});
|
|
1013
1148
|
return h("div", { className: "dshm-links" }, h("span", { className: "dshm-links-k" }, `${lookup("detail.links")}\uFF1A`), ...kids);
|
|
1014
1149
|
}
|
|
1150
|
+
function PhaseDot({ vm }) {
|
|
1151
|
+
if (!vm.phaseDotClass) return null;
|
|
1152
|
+
return h(
|
|
1153
|
+
"span",
|
|
1154
|
+
{ className: `dshm-dot ${vm.phaseDotClass}`, title: vm.phaseLabelKey ? lookup(vm.phaseLabelKey) : null },
|
|
1155
|
+
"\u25CF"
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
function ToggleSwitch({ vm, disabled, onChange, title }) {
|
|
1159
|
+
return h(
|
|
1160
|
+
"label",
|
|
1161
|
+
{
|
|
1162
|
+
className: `dshm-switch${vm.switchOn ? " on" : ""}${vm.switchDisabled ? " locked" : ""}`,
|
|
1163
|
+
title: title || (vm.switchTitleKey ? lookup(vm.switchTitleKey) : null),
|
|
1164
|
+
onClick: (e) => {
|
|
1165
|
+
e.stopPropagation();
|
|
1166
|
+
if (!vm.switchDisabled && !disabled) onChange(!vm.switchOn);
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
h("input", { type: "checkbox", checked: vm.switchOn, disabled: vm.switchDisabled || disabled, readOnly: true }),
|
|
1170
|
+
h("span", { className: "dshm-switch-slider" })
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1015
1173
|
function TwoStepButton({ label, confirmLabel, className, onConfirm, disabled }) {
|
|
1016
1174
|
const [arm, setArm] = useState(false);
|
|
1017
1175
|
useEffect(() => {
|
|
@@ -1105,25 +1263,70 @@ function MarketTab({ notify, market, onMutation }) {
|
|
|
1105
1263
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
1106
1264
|
debounceRef.current = setTimeout(() => updateQuery({ query: value }), 300);
|
|
1107
1265
|
};
|
|
1108
|
-
const
|
|
1266
|
+
const [compatConfirm, setCompatConfirm] = useState(null);
|
|
1267
|
+
const installDone = (res) => {
|
|
1268
|
+
notify({
|
|
1269
|
+
kind: "ok",
|
|
1270
|
+
needsRestart: true,
|
|
1271
|
+
text: lookup("notify.installed", { pkg: res.pkg, version: res.version ? ` v${res.version}` : "" }) + (res.buildApprovals && res.buildApprovals.length ? lookup("notify.builds", { names: res.buildApprovals.join(", ") }) : res.fallbackAllBuilds ? lookup("notify.builds.fallback") : "") + (res.bundleWarning === "no-patch-layer" ? lookup("notify.bundlewarning") : "")
|
|
1272
|
+
});
|
|
1273
|
+
};
|
|
1274
|
+
const doInstall = async (it, version, forceIncompatible) => {
|
|
1109
1275
|
setBusyId(it.id);
|
|
1110
1276
|
try {
|
|
1111
|
-
const res = await api("install", { id: it.id, ...version ? { version } : {} });
|
|
1112
|
-
|
|
1113
|
-
kind: "ok",
|
|
1114
|
-
needsRestart: true,
|
|
1115
|
-
text: lookup("notify.installed", { pkg: res.pkg, version: res.version ? ` v${res.version}` : "" }) + (res.usedAllowAllBuilds ? lookup("notify.allowbuilds") : "")
|
|
1116
|
-
});
|
|
1277
|
+
const res = await api("install", { id: it.id, ...version ? { version } : {}, ...forceIncompatible ? { forceIncompatible: true } : {} });
|
|
1278
|
+
installDone(res);
|
|
1117
1279
|
await (onMutation ? onMutation() : reload(false));
|
|
1118
1280
|
} catch (e) {
|
|
1119
|
-
|
|
1281
|
+
if (e && e.issue) {
|
|
1282
|
+
setCompatConfirm({ it, version, issue: e.issue });
|
|
1283
|
+
} else {
|
|
1284
|
+
notify({ kind: "err", text: lookup("failed.install", { err: e && e.message || e }) });
|
|
1285
|
+
}
|
|
1120
1286
|
} finally {
|
|
1121
1287
|
setBusyId(null);
|
|
1122
1288
|
}
|
|
1123
1289
|
};
|
|
1290
|
+
const CompatDialog = compatConfirm ? h(
|
|
1291
|
+
"div",
|
|
1292
|
+
{ className: "dshm-compat-overlay", onClick: () => setCompatConfirm(null) },
|
|
1293
|
+
h(
|
|
1294
|
+
"div",
|
|
1295
|
+
{ className: "dshm-compat-dialog", onClick: (e) => e.stopPropagation() },
|
|
1296
|
+
h("div", { className: "dshm-compat-title" }, lookup("compat.title")),
|
|
1297
|
+
h("div", { className: "dshm-compat-body" }, lookup("compat.body", {
|
|
1298
|
+
pkg: compatConfirm.issue.pkg || compatConfirm.it.pkg,
|
|
1299
|
+
version: compatConfirm.issue.version || "?",
|
|
1300
|
+
runtime: compatConfirm.issue.runtimeVersion || "?"
|
|
1301
|
+
})),
|
|
1302
|
+
h(
|
|
1303
|
+
"ul",
|
|
1304
|
+
{ className: "dshm-compat-peers" },
|
|
1305
|
+
...Object.entries(compatConfirm.issue.peers || {}).map(([name, range]) => h("li", { key: name }, `${name}: ${range}`))
|
|
1306
|
+
),
|
|
1307
|
+
h("div", { className: "dshm-compat-risk" }, lookup("compat.risk")),
|
|
1308
|
+
h(
|
|
1309
|
+
"div",
|
|
1310
|
+
{ className: "dshm-actions" },
|
|
1311
|
+
h("button", {
|
|
1312
|
+
className: "dshm-btn sm",
|
|
1313
|
+
onClick: () => setCompatConfirm(null)
|
|
1314
|
+
}, lookup("common.cancel")),
|
|
1315
|
+
h("button", {
|
|
1316
|
+
className: "dshm-btn primary sm",
|
|
1317
|
+
onClick: () => {
|
|
1318
|
+
const pending = compatConfirm;
|
|
1319
|
+
setCompatConfirm(null);
|
|
1320
|
+
doInstall(pending.it, pending.version, true);
|
|
1321
|
+
}
|
|
1322
|
+
}, lookup("compat.force"))
|
|
1323
|
+
)
|
|
1324
|
+
)
|
|
1325
|
+
) : null;
|
|
1124
1326
|
return h(
|
|
1125
1327
|
React.Fragment,
|
|
1126
1328
|
null,
|
|
1329
|
+
CompatDialog,
|
|
1127
1330
|
notice ? h(
|
|
1128
1331
|
"div",
|
|
1129
1332
|
{ className: notice.key === "notice.unavailable" ? "dshm-err" : "dshm-hint" },
|
|
@@ -1277,6 +1480,27 @@ function InstalledTab({ notify, installed, onMutation }) {
|
|
|
1277
1480
|
const [openPkg, setOpenPkg] = useState(null);
|
|
1278
1481
|
const [readmePkg, setReadmePkg] = useState(null);
|
|
1279
1482
|
const [busyPkg, setBusyPkg] = useState(null);
|
|
1483
|
+
const doToggle = async (it, enabled) => {
|
|
1484
|
+
setBusyPkg(it.pkg);
|
|
1485
|
+
const call = () => api("set-enabled", { pkg: it.pkg, enabled });
|
|
1486
|
+
try {
|
|
1487
|
+
let res;
|
|
1488
|
+
try {
|
|
1489
|
+
res = await call();
|
|
1490
|
+
} catch (first) {
|
|
1491
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
1492
|
+
res = await call();
|
|
1493
|
+
}
|
|
1494
|
+
const note = toggleNoticeKeys2(res);
|
|
1495
|
+
const extra = res.warnings && res.warnings.length ? `\uFF08${res.warnings.join("\uFF1B")}\uFF09` : "";
|
|
1496
|
+
notify({ kind: "ok", needsRestart: note.needsRestart, text: lookup(note.textKey, note.params) + extra });
|
|
1497
|
+
await (onMutation ? onMutation() : reload());
|
|
1498
|
+
} catch (e) {
|
|
1499
|
+
notify({ kind: "err", text: lookup("toggle.failed", { err: e && e.message || e }) });
|
|
1500
|
+
} finally {
|
|
1501
|
+
setBusyPkg(null);
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1280
1504
|
const doUninstall = async (it) => {
|
|
1281
1505
|
setBusyPkg(it.pkg);
|
|
1282
1506
|
try {
|
|
@@ -1304,7 +1528,7 @@ function InstalledTab({ notify, installed, onMutation }) {
|
|
|
1304
1528
|
pkg: res.pkg,
|
|
1305
1529
|
from: res.fromVersion ? `v${res.fromVersion}` : "\u2014",
|
|
1306
1530
|
to: res.version ? `v${res.version}` : res.sha ? res.sha.slice(0, 7) : "latest"
|
|
1307
|
-
}) + (res.
|
|
1531
|
+
}) + (res.buildApprovals && res.buildApprovals.length ? lookup("notify.builds", { names: res.buildApprovals.join(", ") }) : res.fallbackAllBuilds ? lookup("notify.builds.fallback") : "")
|
|
1308
1532
|
});
|
|
1309
1533
|
await (onMutation ? onMutation() : reload());
|
|
1310
1534
|
} catch (e) {
|
|
@@ -1328,19 +1552,27 @@ function InstalledTab({ notify, installed, onMutation }) {
|
|
|
1328
1552
|
{ className: "dshm-cards" },
|
|
1329
1553
|
items.map((it) => {
|
|
1330
1554
|
const vm = installedViewModel2(it);
|
|
1555
|
+
const tvm = toggleViewModel2(it);
|
|
1331
1556
|
return Card({
|
|
1332
1557
|
key: it.pkg,
|
|
1333
1558
|
icon: h(Icon, { entry: { name: it.name, github: vm.githubRepo, icon: null } }),
|
|
1334
1559
|
name: it.name,
|
|
1560
|
+
topRight: h(ToggleSwitch, {
|
|
1561
|
+
vm: tvm,
|
|
1562
|
+
disabled: busyPkg === it.pkg,
|
|
1563
|
+
onChange: (enabled) => doToggle(it, enabled)
|
|
1564
|
+
}),
|
|
1335
1565
|
badges: [
|
|
1336
1566
|
it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, `\u2B06 ${vm.latestLabel}`.trim()) : null,
|
|
1337
1567
|
it.registryId ? h("span", { className: "dshm-badge", key: "r" }, lookup("badge.market")) : h("span", { className: "dshm-badge info", key: "r" }, lookup("badge.nonmarket"))
|
|
1338
1568
|
],
|
|
1339
1569
|
desc: it.description || "\uFF08\u65E0\u63CF\u8FF0\uFF09",
|
|
1340
1570
|
sub: [
|
|
1571
|
+
h(PhaseDot, { key: "dot", vm: tvm }),
|
|
1572
|
+
vm.phaseKey ? lookup(vm.phaseKey) : lookup(vm.enabledLabelKey),
|
|
1341
1573
|
`v${it.version || "?"}`,
|
|
1342
1574
|
vm.sourceLabelKey ? lookup(vm.sourceLabelKey) : it.source
|
|
1343
|
-
].
|
|
1575
|
+
].map((part, i) => i === 0 ? part : [" \xB7 ", part]).flat(),
|
|
1344
1576
|
links: h(LinksRow, {
|
|
1345
1577
|
npm: it.source === "npm" ? it.pkg : null,
|
|
1346
1578
|
github: vm.githubRepo
|
|
@@ -1399,10 +1631,41 @@ function configStatusLabel(status) {
|
|
|
1399
1631
|
return lookup(`settings.status.${status || "loading"}` || "settings.status.loading");
|
|
1400
1632
|
}
|
|
1401
1633
|
var STATUS_BADGE = { ready: "", pending: "info", rejected: "warn", unavailable: "err", loading: "info" };
|
|
1634
|
+
function probeLabel(statusInfo) {
|
|
1635
|
+
const probe = statusInfo.data && statusInfo.data.probe;
|
|
1636
|
+
if (!probe) return lookup("settings.probe.none");
|
|
1637
|
+
if (probe.source === "npmjs") return lookup("settings.probe.npmjs");
|
|
1638
|
+
if (probe.source === "npmmirror") return lookup("settings.probe.mirror");
|
|
1639
|
+
return lookup("settings.probe.none");
|
|
1640
|
+
}
|
|
1402
1641
|
function SettingsTab({ notify, onRegistryChanged }) {
|
|
1403
1642
|
const reg = useAsync((force) => api("registry", force ? { force: true } : {}), []);
|
|
1404
1643
|
const cfgState = useAsync(() => api("registry-config"), []);
|
|
1405
1644
|
const self = useAsync(() => api("self-check"), []);
|
|
1645
|
+
const statusInfo = useAsync(() => api("status"), []);
|
|
1646
|
+
const probeCfg = useAsync(() => api("probe-config"), []);
|
|
1647
|
+
const [probeDraft, setProbeDraft] = useState(null);
|
|
1648
|
+
const [probeBusy, setProbeBusy] = useState(false);
|
|
1649
|
+
useEffect(() => {
|
|
1650
|
+
if (probeCfg.data && probeDraft === null) setProbeDraft({ ...probeCfg.data });
|
|
1651
|
+
}, [probeCfg.data, probeDraft]);
|
|
1652
|
+
const applyProbe = async () => {
|
|
1653
|
+
if (!probeDraft) return;
|
|
1654
|
+
setProbeBusy(true);
|
|
1655
|
+
try {
|
|
1656
|
+
const patch = {};
|
|
1657
|
+
if (probeDraft.probeEnabled !== void 0) patch.probeEnabled = probeDraft.probeEnabled === true;
|
|
1658
|
+
if (Number.isFinite(Number(probeDraft.probeTimeoutMs))) patch.probeTimeoutMs = Number(probeDraft.probeTimeoutMs);
|
|
1659
|
+
if (Number.isFinite(Number(probeDraft.probeCacheTtlMin))) patch.probeCacheTtlMin = Number(probeDraft.probeCacheTtlMin);
|
|
1660
|
+
const res = await api("probe-config-set", patch);
|
|
1661
|
+
setProbeDraft({ ...res });
|
|
1662
|
+
notify({ kind: "ok", text: lookup("settings.probe.applied"), needsRestart: false });
|
|
1663
|
+
} catch (e) {
|
|
1664
|
+
notify({ kind: "err", text: lookup("settings.probe.failed", { err: e && e.message || e }) });
|
|
1665
|
+
} finally {
|
|
1666
|
+
setProbeBusy(false);
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1406
1669
|
const [busy, setBusy] = useState(false);
|
|
1407
1670
|
const [upgrading, setUpgrading] = useState(false);
|
|
1408
1671
|
const [draftAddress, setDraftAddress] = useState(null);
|
|
@@ -1543,7 +1806,46 @@ function SettingsTab({ notify, onRegistryChanged }) {
|
|
|
1543
1806
|
h("span", { className: "k" }, lookup("settings.count")),
|
|
1544
1807
|
h("span", null, state ? lookup("settings.count.v", { n: state.count ?? 0 }) : "\u2014"),
|
|
1545
1808
|
h("span", { className: "k" }, lookup("settings.policy")),
|
|
1546
|
-
h("span", null, lookup("settings.policy.v"))
|
|
1809
|
+
h("span", null, lookup("settings.policy.v")),
|
|
1810
|
+
h("span", { className: "k" }, lookup("settings.probe")),
|
|
1811
|
+
h("span", null, probeLabel(statusInfo)),
|
|
1812
|
+
h("span", { className: "k" }, lookup("settings.probe.toggle")),
|
|
1813
|
+
h(
|
|
1814
|
+
"span",
|
|
1815
|
+
{ className: "dshm-probe-ctl" },
|
|
1816
|
+
probeDraft ? [
|
|
1817
|
+
h(ToggleSwitch, {
|
|
1818
|
+
key: "sw",
|
|
1819
|
+
vm: { switchOn: probeDraft.probeEnabled === true, switchDisabled: false },
|
|
1820
|
+
title: null,
|
|
1821
|
+
onChange: (v) => setProbeDraft({ ...probeDraft, probeEnabled: v })
|
|
1822
|
+
}),
|
|
1823
|
+
h("input", {
|
|
1824
|
+
key: "timeout",
|
|
1825
|
+
className: "dshm-input",
|
|
1826
|
+
type: "number",
|
|
1827
|
+
style: { maxWidth: "90px" },
|
|
1828
|
+
title: "probeTimeoutMs",
|
|
1829
|
+
value: probeDraft.probeTimeoutMs ?? "",
|
|
1830
|
+
onChange: (e) => setProbeDraft({ ...probeDraft, probeTimeoutMs: e.target.value })
|
|
1831
|
+
}),
|
|
1832
|
+
h("input", {
|
|
1833
|
+
key: "ttl",
|
|
1834
|
+
className: "dshm-input",
|
|
1835
|
+
type: "number",
|
|
1836
|
+
style: { maxWidth: "70px" },
|
|
1837
|
+
title: "probeCacheTtlMin",
|
|
1838
|
+
value: probeDraft.probeCacheTtlMin ?? "",
|
|
1839
|
+
onChange: (e) => setProbeDraft({ ...probeDraft, probeCacheTtlMin: e.target.value })
|
|
1840
|
+
}),
|
|
1841
|
+
h("button", {
|
|
1842
|
+
key: "apply",
|
|
1843
|
+
className: "dshm-btn sm",
|
|
1844
|
+
disabled: probeBusy,
|
|
1845
|
+
onClick: applyProbe
|
|
1846
|
+
}, probeBusy ? h(Spin) : lookup("settings.probe.apply"))
|
|
1847
|
+
] : lookup("settings.probe.none")
|
|
1848
|
+
)
|
|
1547
1849
|
),
|
|
1548
1850
|
state && state.stale && state.status !== "unavailable" ? h("div", { className: "dshm-note" }, lookup("settings.cache.hint")) : null,
|
|
1549
1851
|
state && !state.isDefault ? h("div", { className: "dshm-note warn" }, lookup("settings.trust.hint")) : null,
|
|
@@ -1628,7 +1930,7 @@ function DetailRows(rows) {
|
|
|
1628
1930
|
])
|
|
1629
1931
|
);
|
|
1630
1932
|
}
|
|
1631
|
-
function Card({ icon, name, badges, desc, sub, links, open, onToggle, detail, actions }) {
|
|
1933
|
+
function Card({ icon, name, badges, desc, sub, links, open, onToggle, detail, actions, topRight }) {
|
|
1632
1934
|
return h(
|
|
1633
1935
|
"div",
|
|
1634
1936
|
{
|
|
@@ -1644,7 +1946,7 @@ function Card({ icon, name, badges, desc, sub, links, open, onToggle, detail, ac
|
|
|
1644
1946
|
h(
|
|
1645
1947
|
"div",
|
|
1646
1948
|
{ className: "dshm-meta" },
|
|
1647
|
-
h("div", { className: "dshm-top" }, h("span", { className: "dshm-name" }, name), ...badges.filter(Boolean)),
|
|
1949
|
+
h("div", { className: "dshm-top" }, h("span", { className: "dshm-name" }, name), ...badges.filter(Boolean), topRight || null),
|
|
1648
1950
|
h("div", { className: "dshm-desc", style: open ? { WebkitLineClamp: "unset" } : null }, desc),
|
|
1649
1951
|
sub ? h("div", { className: "dshm-sub" }, sub) : null,
|
|
1650
1952
|
links || null,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 精确构建放行(0.4.0,ADR-0002;plan Task 3):
|
|
3
|
+
* pnpm 11 拦下依赖构建脚本时会在 `pnpm-workspace.yaml` 的 `allowBuilds` 映射里留下
|
|
4
|
+
* 待决占位(值为 "set this to true or false")。本模块提供两个纯函数:
|
|
5
|
+
*
|
|
6
|
+
* - readPendingBuilds:解析待决名单(键不含通配符、值精确等于占位串)——官方
|
|
7
|
+
* `readPendingBuilds`(dsh-plugin-manager L941)同款判定;官方对坏结构 throw,
|
|
8
|
+
* 此处收窄为返回 [](调用方据此走全量兜底,ADR-0002 的保守回退);
|
|
9
|
+
* - applyPreciseBuilds:为名单逐键写 `allowBuilds.<name>: true`(保注释 YAML 编辑)。
|
|
10
|
+
*
|
|
11
|
+
* 官方 `approveBuilds` 的调用前提是「caller 持 profile manifest 锁」;dsh-m 事务
|
|
12
|
+
* 只有进程内 FIFO,与官方 UI 并发安装时此处存在低概率写竞态窗口——按 ADR-0002
|
|
13
|
+
* 声明为已知风险,相关整单失败走既有回滚语义,不额外加锁。
|
|
14
|
+
*/
|
|
15
|
+
import { isAlias, isMap, isScalar, isSeq, parseDocument } from 'yaml';
|
|
16
|
+
/** pnpm 11 写下的待决占位值(逐字,官方同款)。 */
|
|
17
|
+
const PENDING_PLACEHOLDER = 'set this to true or false';
|
|
18
|
+
/** 键含通配符的规则不进精确放行(官方同款排除)。 */
|
|
19
|
+
function isPlainKey(key) {
|
|
20
|
+
return !/[*?]/.test(key);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 读 pnpm-workspace.yaml 文本的待决构建名单。
|
|
24
|
+
* 保守口径:坏 YAML / 根非映射 / allowBuilds 非映射 / 条目含锚点或别名 / 值非标量
|
|
25
|
+
* 一律返回 []——调用方走 `withDangerouslyAllowAllBuilds` 全量兜底并如实标注。
|
|
26
|
+
*/
|
|
27
|
+
export function readPendingBuilds(text) {
|
|
28
|
+
let document;
|
|
29
|
+
try {
|
|
30
|
+
document = parseDocument(text);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
if (document.errors.length > 0)
|
|
36
|
+
return [];
|
|
37
|
+
if (!isMap(document.contents))
|
|
38
|
+
return [];
|
|
39
|
+
const builds = document.contents.get('allowBuilds');
|
|
40
|
+
if (builds === undefined || builds === null)
|
|
41
|
+
return [];
|
|
42
|
+
if (!isMap(builds))
|
|
43
|
+
return [];
|
|
44
|
+
const pending = [];
|
|
45
|
+
for (const pair of builds.items) {
|
|
46
|
+
const { key, value } = pair;
|
|
47
|
+
if (!isScalar(key) || typeof key.value !== 'string')
|
|
48
|
+
continue;
|
|
49
|
+
if (isAlias(key) || isAlias(value))
|
|
50
|
+
return [];
|
|
51
|
+
// 锚点定义同样拒绝(官方 visit 检查的保守化)
|
|
52
|
+
if ((isMap(value) || isSeq(value)) && 'anchor' in value && value.anchor)
|
|
53
|
+
return [];
|
|
54
|
+
if (!isScalar(value) || value.value !== PENDING_PLACEHOLDER)
|
|
55
|
+
continue;
|
|
56
|
+
if (!isPlainKey(key.value))
|
|
57
|
+
continue;
|
|
58
|
+
pending.push(key.value);
|
|
59
|
+
}
|
|
60
|
+
return pending;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 为名单逐键写 `allowBuilds.<name>: true`(保注释;allowBuilds 缺失时创建)。
|
|
64
|
+
* 坏 YAML / 根非映射 → throw(调用方按放行写入失败处理,走既有 hard-fail 转换契约)。
|
|
65
|
+
* names 为空返回原文。
|
|
66
|
+
*/
|
|
67
|
+
export function applyPreciseBuilds(text, names) {
|
|
68
|
+
if (names.length === 0)
|
|
69
|
+
return text;
|
|
70
|
+
const document = parseDocument(text);
|
|
71
|
+
const error = document.errors[0];
|
|
72
|
+
if (error !== undefined)
|
|
73
|
+
throw error;
|
|
74
|
+
if (!isMap(document.contents))
|
|
75
|
+
throw new Error('pnpm-workspace.yaml 必须是 YAML 映射');
|
|
76
|
+
const existing = document.contents.get('allowBuilds');
|
|
77
|
+
if (existing !== undefined && existing !== null && !isMap(existing)) {
|
|
78
|
+
throw new Error('allowBuilds 必须是 YAML 映射');
|
|
79
|
+
}
|
|
80
|
+
for (const name of names) {
|
|
81
|
+
if (typeof name !== 'string' || !isPlainKey(name)) {
|
|
82
|
+
throw new Error(`精确放行拒绝通配/非法键: ${JSON.stringify(name)}`);
|
|
83
|
+
}
|
|
84
|
+
// setIn 自动创建缺失的中间映射(allowBuilds 缺失时一并创建)
|
|
85
|
+
document.setIn(['allowBuilds', name], true);
|
|
86
|
+
}
|
|
87
|
+
return String(document);
|
|
88
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* peer 兼容预检(0.4.0,plan Task 6;语义逐条对齐官方 evaluatePluginCompatibility
|
|
3
|
+
* ——dsh-app-boot L286-313,源码在 app-boot 而非 plugin-manager 本体):
|
|
4
|
+
*
|
|
5
|
+
* - 只检 `@deepseek-ai/dsh` 与 `@deepseek-ai/dsh-*` 键;
|
|
6
|
+
* - `workspace:^` / `workspace:~` / `workspace:*` 视为当前运行时版本(workspace 依赖
|
|
7
|
+
* 在发布时由运行时替换);
|
|
8
|
+
* - 空 range 不满足;semver satisfies 带 includePrerelease(rc 生态必需);
|
|
9
|
+
* - 运行时版本解析失败(null)→ 不拦(结果由调用方注明「未检」);
|
|
10
|
+
* - github 源不在此路径(无 packument),由 market 层提示 compatSkipped;
|
|
11
|
+
* - 无豁免机制(grilling Q6 定稿):确认/force 即通道。
|
|
12
|
+
*/
|
|
13
|
+
import { satisfies } from 'semver';
|
|
14
|
+
import { npmVersion } from './versions.js';
|
|
15
|
+
import { resolveDshVersion } from './dsh-version.js';
|
|
16
|
+
const WORKSPACE_SPECS = ['workspace:^', 'workspace:~', 'workspace:*'];
|
|
17
|
+
export class IncompatibleError extends Error {
|
|
18
|
+
issue;
|
|
19
|
+
constructor(issue) {
|
|
20
|
+
super(`插件 ${issue.pkg}@${issue.version} 与 DSH ${issue.runtimeVersion} 不兼容(peerDependencies: ${JSON.stringify(issue.peers)})`);
|
|
21
|
+
this.issue = issue;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 纯函数:peers 中不满足运行时版本的 dsh 系键(空对象 = 兼容)。
|
|
26
|
+
* 非法 range 按 semver 抛错口径让位给 satisfies(异常上抛,调用方如实报告)。
|
|
27
|
+
*/
|
|
28
|
+
export function evaluatePeers(peers, runtimeVersion) {
|
|
29
|
+
const failing = {};
|
|
30
|
+
for (const [name, range] of Object.entries(peers)) {
|
|
31
|
+
if (typeof range !== 'string')
|
|
32
|
+
continue;
|
|
33
|
+
if (name !== '@deepseek-ai/dsh' && !name.startsWith('@deepseek-ai/dsh-'))
|
|
34
|
+
continue;
|
|
35
|
+
const requirement = WORKSPACE_SPECS.includes(range) ? runtimeVersion : range;
|
|
36
|
+
if (requirement.trim() === '' || !satisfies(runtimeVersion, requirement, { includePrerelease: true })) {
|
|
37
|
+
failing[name] = range;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return failing;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* npm 源预检:拉该精确版本的 peerDependencies 与运行时版本比对。
|
|
44
|
+
* 返回 null = 兼容或无法判定(runtime 解析失败 → 不拦)。
|
|
45
|
+
*/
|
|
46
|
+
export async function precheckNpmCompat(pkg, version, opts) {
|
|
47
|
+
const runtime = opts.runtimeVersion !== undefined ? opts.runtimeVersion : await resolveDshVersion();
|
|
48
|
+
if (runtime === null || runtime === '')
|
|
49
|
+
return null;
|
|
50
|
+
const fetchVersion = opts.fetchVersion ?? npmVersion;
|
|
51
|
+
const meta = await fetchVersion(pkg, version, opts.timeoutMs, opts.signal, opts.registry);
|
|
52
|
+
const failing = evaluatePeers(meta.peers ?? {}, runtime);
|
|
53
|
+
if (Object.keys(failing).length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
return { pkg, version, runtimeVersion: runtime, peers: failing };
|
|
56
|
+
}
|