@1e0zj/dsh-plugin-mall 0.4.16 → 0.4.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1e0zj/dsh-plugin-mall",
3
- "version": "0.4.16",
3
+ "version": "0.4.17",
4
4
  "description": "dsh 插件市场:搜索 GitHub dsh-plugin 话题下的插件仓库,一键安装到本地 dsh profile(agent 工具 + 设置页插件市场 tab)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/client.js CHANGED
@@ -162,8 +162,12 @@ window.__ModuleLoader__.load({
162
162
  // ——锁必须持续到 job 落终态,不是到 RPC 返回。
163
163
  // needsApproval 的 status 是 failed,但那是等用户决定的暂停,
164
164
  // 事务没结束,算未了结。
165
- // card 预检出了风险卡片,停在那里等用户读完再决定。这期间任务是
165
+ // cards 预检出了风险卡片,停在那里等用户读完再决定。这期间任务是
166
166
  // 终态,但决定还没做,再起一条链就是两条链抢同一个事务。
167
+ // **按 jobId 存字典、不是单槽位**:同时可以停着好几张卡片
168
+ // (批量更新必然如此,手动连点两个包也够)。单槽位时后来的
169
+ // 会顶掉前一张,被顶掉那条从此没有任何入口能做决定,job 却
170
+ // 一直是未了结状态——那一行的更新按钮再也点不亮。
167
171
  //
168
172
  // sources 里递进来的必须是 ref.current 这类当下的值:判定发生在点击那一
169
173
  // 刻,上一次渲染的快照没有意义。
@@ -174,8 +178,11 @@ window.__ModuleLoader__.load({
174
178
  for (var spec in installing) {
175
179
  if (installing[spec] === true && specPackageName(spec) === name) return true;
176
180
  }
177
- var card = sources.card;
178
- if (card && specPackageName(card.spec) === name) return true;
181
+ var cards = sources.cards || {};
182
+ for (var cardId in cards) {
183
+ var card = cards[cardId];
184
+ if (card && specPackageName(card.spec) === name) return true;
185
+ }
179
186
  var jobs = sources.jobs || {};
180
187
  for (var id in jobs) {
181
188
  var job = jobs[id];
@@ -664,7 +671,7 @@ window.__ModuleLoader__.load({
664
671
  if (ids.length === 0 && !props.error) return null;
665
672
  var hasSettled = ids.some(function (id) {
666
673
  var job = props.jobs[id];
667
- var hasDecision = (props.preflight && props.preflight.jobId === id)
674
+ var hasDecision = props.preflights[id] !== undefined
668
675
  || (job.needsApproval && job.needsApproval.length > 0);
669
676
  return !isJobActive(job.status) && !hasDecision;
670
677
  });
@@ -677,7 +684,7 @@ window.__ModuleLoader__.load({
677
684
  ids.map(function (id) {
678
685
  var job = props.jobs[id];
679
686
  var done = job.status === "completed" || job.status === "failed" || job.status === "killed";
680
- var preflightDecision = props.preflight && props.preflight.jobId === id ? props.preflight : null;
687
+ var preflightDecision = props.preflights[id] || null;
681
688
  var awaitingDecision = preflightDecision !== null || (job.needsApproval && job.needsApproval.length > 0);
682
689
  return h("div", { key: id, className: "mkt_job" },
683
690
  // 不再显示 market-N 这种内部 id——对用户没有意义,反而让人以为
@@ -697,12 +704,12 @@ window.__ModuleLoader__.load({
697
704
  ? h(ApprovalRequest, {
698
705
  spec: job.spec,
699
706
  needsApproval: job.needsApproval,
700
- busy: props.approving === job.spec,
707
+ busy: props.specBusy(job.spec),
701
708
  onApprove: function (names) {
702
709
  // 不先 drop:旧条目由重试任务的 track(carryFromId) 原子接管
703
710
  // (撤条目 + 日志接续一拍完成)。install RPC 现在只做本地校验、
704
711
  // 立刻返回新 job id,但保留这个次序仍然是它最稳的形态——
705
- // 万一 RPC 失败,旧条目和日志还在。等待期间按钮由 approving
712
+ // 万一 RPC 失败,旧条目和日志还在。等待期间按钮由 specBusy
706
713
  // 态显示「继续中…」。
707
714
  props.onApprove(job.spec, names, job.approvalToken, id);
708
715
  },
@@ -712,9 +719,9 @@ window.__ModuleLoader__.load({
712
719
  ? h(PreflightCard, {
713
720
  spec: preflightDecision.spec,
714
721
  report: preflightDecision.report,
715
- busy: props.preflightBusy === true,
716
- onConfirm: props.onConfirmPreflight,
717
- onClose: props.onClosePreflight,
722
+ busy: props.specBusy(preflightDecision.spec),
723
+ onConfirm: function () { props.onConfirmPreflight(id); },
724
+ onClose: function () { props.onClosePreflight(id); },
718
725
  })
719
726
  : job.detail ? h("div", { className: job.status === "failed" ? "mkt_error" : "mkt_desc" }, job.detail) : null,
720
727
  // 预检完成不提示重启——它没有改动任何东西,接下来才是安装。
@@ -873,17 +880,27 @@ window.__ModuleLoader__.load({
873
880
  var _hostStartedAt = useState(0);
874
881
  var hostStartedAt = _hostStartedAt[0];
875
882
  var setHostStartedAt = _hostStartedAt[1];
876
- var _preflight = useState(null);
877
- var preflight = _preflight[0];
878
- var setPreflight = _preflight[1];
883
+ // jobId -> { spec, report, jobId }。字典而不是单个:见 updateChainBusy
884
+ // cards 一节——同时可以有好几张待决策的风险卡片。
885
+ var _preflights = useState({});
886
+ var preflights = _preflights[0];
887
+ var setPreflights = _preflights[1];
888
+ var dropPreflight = useCallback(function (jobId) {
889
+ setPreflights(function (prev) {
890
+ if (!(jobId in prev)) return prev;
891
+ var next = Object.assign({}, prev);
892
+ delete next[jobId];
893
+ return next;
894
+ });
895
+ }, []);
879
896
 
880
897
  // 更新按钮防连点用的三份当下值(见 updateChainBusy)。ref 而不是 state:
881
898
  // 判定发生在点击那一刻,而 setState 到重渲染之间还有一拍能点。ref 不触发
882
899
  // 渲染,所以每次改动配一次 bumpBusy,让按钮当场变样。
883
900
  var updatingRef = useRef({}); // 包名 -> true,点击到预检 job 建立之间
884
901
  var installingRef = useRef({}); // spec -> true,安装 RPC 在途
885
- var preflightRef = useRef(null);
886
- preflightRef.current = preflight; // 渲染期同步,同 resultsRef 的写法
902
+ var preflightsRef = useRef({});
903
+ preflightsRef.current = preflights; // 渲染期同步,同 resultsRef 的写法
887
904
  var _busyTick = useState(0);
888
905
  var setBusyTick = _busyTick[1];
889
906
  var bumpBusy = useCallback(function () {
@@ -1064,7 +1081,7 @@ window.__ModuleLoader__.load({
1064
1081
  return updateChainBusy({
1065
1082
  updating: updatingRef.current,
1066
1083
  installing: installingRef.current,
1067
- card: preflightRef.current,
1084
+ cards: preflightsRef.current,
1068
1085
  jobs: jobsRef.current,
1069
1086
  }, packageName);
1070
1087
  }, [jobsRef]);
@@ -1131,7 +1148,12 @@ window.__ModuleLoader__.load({
1131
1148
  // 安全:接过预检的日志、撤掉预检条目,面板上只剩这一个任务。
1132
1149
  doRawInstall(spec, {}, jobId);
1133
1150
  } else {
1134
- setPreflight({ spec: spec, report: report, jobId: jobId });
1151
+ // jobId 落格,不覆盖别的包停着的卡片。
1152
+ setPreflights(function (prev) {
1153
+ var next = Object.assign({}, prev);
1154
+ next[jobId] = { spec: spec, report: report, jobId: jobId };
1155
+ return next;
1156
+ });
1135
1157
  }
1136
1158
  };
1137
1159
  }, [doRawInstall, reportError]);
@@ -1306,12 +1328,15 @@ window.__ModuleLoader__.load({
1306
1328
  h(JobsPanel, {
1307
1329
  jobs: jobs,
1308
1330
  error: error && error.scope === "tasks" ? error.message : null,
1309
- preflight: preflight,
1310
- preflightBusy: preflight ? installing[preflight.spec] === true : false,
1331
+ preflights: preflights,
1332
+ // 这张卡片/这条批准请求对应的 install RPC 在不在途。原来是「取
1333
+ // installing 里的第一个 spec」的单值,同时两条链在途时只有一条显示
1334
+ // 得对;按 spec 问就没有这个假设了。
1335
+ specBusy: function (spec) { return installing[spec] === true; },
1311
1336
  onClear: function () {
1312
1337
  Object.keys(jobs).forEach(function (id) {
1313
1338
  var job = jobs[id];
1314
- var hasDecision = job && ((preflight && preflight.jobId === id)
1339
+ var hasDecision = job && (preflights[id] !== undefined
1315
1340
  || (job.needsApproval && job.needsApproval.length > 0));
1316
1341
  if (!job || isJobActive(job.status) || hasDecision) return;
1317
1342
  var token = job && job.approvalToken ? job.approvalToken : (job && job.spec ? approvalTokensRef.current[job.spec] : undefined);
@@ -1330,33 +1355,34 @@ window.__ModuleLoader__.load({
1330
1355
  });
1331
1356
  },
1332
1357
  onApprove: doApprove,
1333
- onConfirmPreflight: function () {
1334
- if (!preflight) return;
1335
- var spec = preflight.spec;
1336
- var carry = preflight.jobId;
1337
- var report = preflight.report;
1338
- setPreflight(null);
1358
+ onConfirmPreflight: function (jobId) {
1359
+ var decision = preflights[jobId];
1360
+ if (!decision) return;
1361
+ dropPreflight(jobId);
1339
1362
  // consentDigest 来自预检 job 的 extras,装的时候与当前报告比对:
1340
1363
  // 用户点「继续」到安装真正开跑之间候选包或 profile 变了,
1341
1364
  // 布尔同意不得沿用,要重新看新的警告。
1342
- doRawInstall(spec, { acceptWarnings: true, acceptedReportDigest: report.consentDigest }, carry);
1365
+ doRawInstall(decision.spec, { acceptWarnings: true, acceptedReportDigest: decision.report.consentDigest }, jobId);
1343
1366
  },
1344
- onClosePreflight: function () {
1345
- if (preflight && preflight.spec) delete approvalTokensRef.current[preflight.spec];
1346
- setPreflight(null);
1367
+ onClosePreflight: function (jobId) {
1368
+ var decision = preflights[jobId];
1369
+ if (decision && decision.spec) delete approvalTokensRef.current[decision.spec];
1370
+ dropPreflight(jobId);
1347
1371
  },
1348
1372
  onDismiss: function (id) {
1349
1373
  var job = jobs[id];
1350
1374
  var token = job && job.approvalToken ? job.approvalToken : (job && job.spec ? approvalTokensRef.current[job.spec] : undefined);
1351
1375
  if (job && job.spec) delete approvalTokensRef.current[job.spec];
1352
1376
  call("jobDismiss", { jobId: id, token: token }).catch(function () {});
1377
+ // job 条目撤了,挂在它上面的卡片没有宿主了。不一并撤的话
1378
+ // updateChainBusy 的 cards 分支会一直锁着那个包名。
1379
+ dropPreflight(id);
1353
1380
  dropJob(id);
1354
1381
  },
1355
1382
  onDrop: dropJob,
1356
1383
  onRestart: function () { setRestartConfirm(true); },
1357
1384
  restarting: restarting,
1358
1385
  hostStartedAt: hostStartedAt,
1359
- approving: Object.keys(installing).filter(function (s) { return installing[s]; })[0],
1360
1386
  }),
1361
1387
  h("div", { className: "mkt_list" },
1362
1388
  results == null
package/src/index.js CHANGED
@@ -3329,7 +3329,7 @@ export async function runSelfTests() {
3329
3329
  ].join("\n"))();
3330
3330
 
3331
3331
  const busy = (patch, name) => front.updateChainBusy(
3332
- Object.assign({ updating: {}, installing: {}, card: null, jobs: {} }, patch), name);
3332
+ Object.assign({ updating: {}, installing: {}, cards: {}, jobs: {} }, patch), name);
3333
3333
  const running = (spec) => ({ spec, status: "running" });
3334
3334
 
3335
3335
  check("更新防连点:裸包名折算", front.specPackageName("foo") === "foo");
@@ -3350,7 +3350,15 @@ export async function runSelfTests() {
3350
3350
  check("更新防连点:停在批准卡片上(failed + needsApproval)算未了结",
3351
3351
  busy({ jobs: { a: { spec: "foo@1.2.3", status: "failed", needsApproval: [{ name: "x" }] } } }, "foo") === true);
3352
3352
  check("更新防连点:停在风险卡片上时锁着(决定还没做,再起一条就是两条链)",
3353
- busy({ card: { spec: "foo@1.2.3" } }, "foo") === true);
3353
+ busy({ cards: { j1: { spec: "foo@1.2.3" } } }, "foo") === true);
3354
+ // 单槽位时代的真实卡死:第二张卡片顶掉第一张,被顶掉那条再没有入口能
3355
+ // 做决定,job 却一直未了结——那一行的更新按钮永远点不亮。
3356
+ check("更新防连点:多张卡片同时停着,各锁各的包(批量更新的必然形状)",
3357
+ busy({ cards: { j1: { spec: "foo@1.2.3" }, j2: { spec: "bar@2.0.0" } } }, "foo") === true
3358
+ && busy({ cards: { j1: { spec: "foo@1.2.3" }, j2: { spec: "bar@2.0.0" } } }, "bar") === true);
3359
+ check("更新防连点:撤掉其中一张不影响另一张的锁",
3360
+ busy({ cards: { j2: { spec: "bar@2.0.0" } } }, "foo") === false
3361
+ && busy({ cards: { j2: { spec: "bar@2.0.0" } } }, "bar") === true);
3354
3362
 
3355
3363
  check("更新防连点:job 落终态后解锁,用户可以重试",
3356
3364
  busy({ jobs: { a: { spec: "foo@1.2.3", status: "failed" } } }, "foo") === false);
@@ -3359,7 +3367,7 @@ export async function runSelfTests() {
3359
3367
  check("更新防连点:别的包在跑不锁本行",
3360
3368
  busy({ jobs: { a: running("bar@1.0.0") } }, "foo") === false);
3361
3369
  check("更新防连点:风险卡片停在别的包上不锁本行",
3362
- busy({ card: { spec: "bar@1.0.0" } }, "foo") === false);
3370
+ busy({ cards: { j1: { spec: "bar@1.0.0" } } }, "foo") === false);
3363
3371
  check("更新防连点:什么都没在途 → 不锁", busy({}, "foo") === false);
3364
3372
  check("更新防连点:包名为 null 时不误判",
3365
3373
  busy({ jobs: { a: running("foo@1.2.3") } }, null) === false);