dsh-plugin-upgrader 1.0.2 → 1.1.1

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/lib/client.js CHANGED
@@ -61,6 +61,9 @@ window.__ModuleLoader__.load({
61
61
  upToDate: "\u5168\u90E8\u5DF2\u662F\u6700\u65B0",
62
62
  upgrade: "\u5347\u7EA7",
63
63
  upgrading: "\u5347\u7EA7\u4E2D\u2026",
64
+ selfUpdate: "\u7528\u9875\u9762\u5B89\u88C5\u5668\u5347\u7EA7",
65
+ selfUpdateHint: "\u63D2\u4EF6\u4E0D\u80FD\u5378\u8F7D\u81EA\u5DF1\u518D\u88C5\u56DE\u81EA\u5DF1\uFF0C\u6240\u4EE5\u8FD9\u4E00\u6B65\u4EA4\u7ED9\u63D2\u4EF6\u9875\u81EA\u5DF1\u7684\u5B89\u88C5\u6D41\u7A0B",
66
+ selfUpdateUnavailable: "\u8FD9\u4E00\u9875\u6CA1\u6709\u63D0\u4F9B\u5B89\u88C5\u5165\u53E3\uFF0C\u8BF7\u5728\u300C\u5DF2\u5B89\u88C5\u300D\u91CC\u7528\u5B98\u65B9\u5B89\u88C5\u5BF9\u8BDD\u6846\u5347\u7EA7\u672C\u63D2\u4EF6\u3002",
64
67
  history: "\u5386\u53F2\u7248\u672C",
65
68
  hideHistory: "\u6536\u8D77\u5386\u53F2",
66
69
  github: "GitHub \u4E3B\u9875",
@@ -82,19 +85,32 @@ window.__ModuleLoader__.load({
82
85
  openPage: "\u5728\u5DF2\u5B89\u88C5\u4E2D\u67E5\u770B",
83
86
  noHistory: "\u6CA1\u6709\u53EF\u7528\u7684\u7248\u672C\u8BB0\u5F55",
84
87
  noHistoryWhy: "\u8FD9\u4E2A\u5305\u4E0D\u662F\u4ECE npm \u6CE8\u518C\u8868\u5B89\u88C5\u7684\uFF0C\u6CA1\u6709\u53D1\u5E03\u5386\u53F2\u3002",
88
+ historyFailed: "\u7248\u672C\u5217\u8868\u8BFB\u53D6\u5931\u8D25\uFF0C\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u53EF\u91CD\u8BD5",
85
89
  versions: (n) => `${n} \u4E2A\u7248\u672C`,
86
90
  showingNewest: (n) => `\u5217\u51FA\u6700\u65B0 ${n} \u4E2A`,
87
91
  progress: (done, total) => `\u6B63\u5728\u5904\u7406 ${done}/${total}`,
92
+ elapsed: (text) => `\u5DF2\u7528\u65F6 ${text}`,
93
+ phaseInstalling: "\u6B63\u5728\u4E0B\u8F7D\u5E76\u5B89\u88C5",
94
+ phaseApplying: "\u6B63\u5728\u5199\u5165\u914D\u7F6E",
95
+ phaseCancelling: "\u6B63\u5728\u53D6\u6D88",
96
+ phaseWaiting: "\u51C6\u5907\u4E2D",
97
+ registryAttempt: (index, total) => `\u6CE8\u518C\u8868 ${index}/${total}\uFF0C\u4E0A\u4E00\u4E2A\u8D85\u65F6\u4F1A\u81EA\u52A8\u6362\u6E90`,
98
+ slowHint: "\u9996\u6B21\u5B89\u88C5\u6216\u9700\u8981\u6821\u9A8C\u4F9D\u8D56\u65F6\u4F1A\u6BD4\u8F83\u6162\uFF0C\u65E5\u5FD7\u91CC\u80FD\u770B\u5230\u771F\u5B9E\u8FDB\u5EA6\u3002",
99
+ cancelRequested: "\u5DF2\u8BF7\u6C42\u53D6\u6D88",
100
+ stalled: "\u8FD9\u4E00\u6B65\u5DF2\u7ECF\u5F88\u4E45\u6CA1\u6709\u8F93\u51FA\u4E86\uFF0C\u53EF\u4EE5\u53D6\u6D88\u540E\u91CD\u8BD5\u3002",
101
+ staleSnapshot: "\u72B6\u6001\u5DF2\u505C\u6B62\u66F4\u65B0\uFF0C\u8BF7\u68C0\u67E5\u66F4\u65B0",
88
102
  cancel: "\u53D6\u6D88",
89
103
  cancelling: "\u6B63\u5728\u53D6\u6D88\u2026",
90
104
  restart: "\u9700\u8981\u91CD\u542F DSH \u624D\u4F1A\u751F\u6548\u3002",
91
105
  restartHint: "\u5DF2\u4FDD\u5B58\u7684\u6539\u52A8\u5728\u4E0B\u6B21\u542F\u52A8\u65F6\u751F\u6548\u3002",
106
+ reloadForAppearance: "\u4E3B\u9898\u63D2\u4EF6\u5DF2\u66F4\u65B0\uFF0C\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u9875\u9762\u4EE5\u6062\u590D\u4E3B\u9898\u5916\u89C2\uFF08\u4E3B\u9898\u914D\u7F6E\u4FDD\u5B58\u5728\u78C1\u76D8\u4E0A\uFF0C\u4E0D\u4F1A\u4E22\u5931\uFF09\u3002",
92
107
  done: "\u5B8C\u6210",
93
108
  failed: "\u5931\u8D25",
94
109
  failedSome: (n) => `${n} \u4E2A\u5931\u8D25`,
95
110
  jobDone: "\u6279\u91CF\u5347\u7EA7\u5B8C\u6210",
96
111
  jobCancelled: "\u5DF2\u53D6\u6D88",
97
112
  busyAdopted: "\u5DF2\u6709\u4E00\u6279\u5347\u7EA7\u6B63\u5728\u8FDB\u884C\uFF0C\u5DF2\u63A5\u7740\u663E\u793A\u5B83\u7684\u8FDB\u5EA6\u3002",
113
+ lostJob: "\u5347\u7EA7\u8FDB\u5EA6\u5DF2\u5931\u53BB\u8054\u7CFB\uFF08\u5BBF\u4E3B\u53EF\u80FD\u91CD\u542F\u8FC7\uFF09\u3002\u8BF7\u68C0\u67E5\u66F4\u65B0\uFF0C\u786E\u8BA4\u63D2\u4EF6\u5F53\u524D\u7248\u672C\u3002",
98
114
  log: "\u8FD0\u884C\u65E5\u5FD7",
99
115
  showLog: "\u67E5\u770B\u65E5\u5FD7",
100
116
  hideLog: "\u6536\u8D77\u65E5\u5FD7",
@@ -126,6 +142,9 @@ window.__ModuleLoader__.load({
126
142
  upToDate: "Everything is up to date",
127
143
  upgrade: "Upgrade",
128
144
  upgrading: "Upgrading\u2026",
145
+ selfUpdate: "Upgrade via the page",
146
+ selfUpdateHint: "A plugin cannot uninstall and reinstall itself, so this runs the plugin page own installer",
147
+ selfUpdateUnavailable: "This page offers no install entry; upgrade this plugin from the Installed tab instead.",
129
148
  history: "Version history",
130
149
  hideHistory: "Hide history",
131
150
  github: "GitHub page",
@@ -147,19 +166,32 @@ window.__ModuleLoader__.load({
147
166
  openPage: "Open in Installed",
148
167
  noHistory: "No published versions found",
149
168
  noHistoryWhy: "This package was not installed from an npm registry, so it has no published history.",
169
+ historyFailed: 'Could not read the version list; "Check for updates" retries it',
150
170
  versions: (n) => `${n} versions`,
151
171
  showingNewest: (n) => `showing newest ${n}`,
152
172
  progress: (done, total) => `Working on ${done}/${total}`,
173
+ elapsed: (text) => `${text} elapsed`,
174
+ phaseInstalling: "Downloading and installing",
175
+ phaseApplying: "Writing the profile",
176
+ phaseCancelling: "Cancelling",
177
+ phaseWaiting: "Preparing",
178
+ registryAttempt: (index, total) => `Registry ${index}/${total}; a timeout falls back to the next`,
179
+ slowHint: "A first install, or one that must verify dependencies, takes a while \u2014 the log shows what is happening.",
180
+ cancelRequested: "Cancellation requested",
181
+ stalled: "This step has produced no output for a long time; cancel it and retry.",
182
+ staleSnapshot: "status stopped updating \u2014 check for updates",
153
183
  cancel: "Cancel",
154
184
  cancelling: "Cancelling\u2026",
155
185
  restart: "Restart DSH to finish.",
156
186
  restartHint: "Saved changes take effect on the next start.",
187
+ reloadForAppearance: "The theme plugin was updated; reloading the page to restore its look (its settings are on disk and are not lost).",
157
188
  done: "done",
158
189
  failed: "failed",
159
190
  failedSome: (n) => `${n} failed`,
160
191
  jobDone: "Batch upgrade finished",
161
192
  jobCancelled: "Cancelled",
162
193
  busyAdopted: "A batch is already running; showing its progress instead.",
194
+ lostJob: "Lost contact with the upgrade (the host may have restarted). Check for updates to confirm the installed version.",
163
195
  log: "Run log",
164
196
  showLog: "Show log",
165
197
  hideLog: "Hide log",
@@ -205,6 +237,16 @@ window.__ModuleLoader__.load({
205
237
  window.open(url, "_blank", "noopener,noreferrer");
206
238
  }
207
239
  }
240
+ function formatElapsed(ms) {
241
+ if (!Number.isFinite(ms) || ms < 0) return "0s";
242
+ const total = Math.floor(ms / 1e3);
243
+ if (total < 60) return `${total}s`;
244
+ const minutes = Math.floor(total / 60);
245
+ const seconds = total % 60;
246
+ if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
247
+ const hours = Math.floor(minutes / 60);
248
+ return `${hours}h ${String(minutes % 60).padStart(2, "0")}m`;
249
+ }
208
250
  function resolveVersionLink(row, version) {
209
251
  const raw = typeof version.href === "string" ? version.href : typeof version.url === "string" && typeof row.repoUrl === "string" && version.url.startsWith("/") ? `${row.repoUrl}${version.url}` : version.url;
210
252
  return typeof raw === "string" && /^https?:\/\//i.test(raw) ? raw : void 0;
@@ -335,6 +377,16 @@ window.__ModuleLoader__.load({
335
377
  );
336
378
  return alive;
337
379
  }
380
+ var APPEARANCE_PATTERN = /(^|[/@-])(skin|theme|dream-skin|wallpaper|appearance)([/@.-]|$)/i;
381
+ function isAppearancePackage(name2) {
382
+ return APPEARANCE_PATTERN.test(name2);
383
+ }
384
+ function replacedAppearance(results) {
385
+ if (!Array.isArray(results)) return false;
386
+ return results.some(
387
+ (result) => result.ok === true && result.changed !== false && typeof result.name === "string" && isAppearancePackage(result.name)
388
+ );
389
+ }
338
390
 
339
391
  // src/client/styles.ts
340
392
  var STYLE_ID = "dsh-plugin-upgrader-style";
@@ -352,7 +404,7 @@ window.__ModuleLoader__.load({
352
404
  .dsh-pu-btn { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 12px; border-radius: 8px; font-size: 12px; font-weight: 500; cursor: pointer; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); transition: background .15s ease, border-color .15s ease, opacity .15s ease; font-family: inherit; }
353
405
  .dsh-pu-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); border-color: var(--dsw-alias-border-l2); }
354
406
  .dsh-pu-btn:disabled { opacity: .5; cursor: default; }
355
- .dsh-pu-btn.is-primary { background: var(--dsw-alias-button-primary-fill); border-color: transparent; color: #fff; }
407
+ .dsh-pu-btn.is-primary { background: var(--dsw-alias-button-primary-fill); border-color: transparent; color: var(--dsw-alias-label-primary-inverted, #fff); }
356
408
  .dsh-pu-btn.is-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover); }
357
409
  .dsh-pu-btn.is-ghost { background: transparent; border-color: transparent; color: var(--dsw-alias-label-secondary); padding: 0 8px; }
358
410
  .dsh-pu-btn.is-ghost:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
@@ -384,6 +436,8 @@ window.__ModuleLoader__.load({
384
436
  .dsh-pu-name { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 8px; min-width: 0; }
385
437
  .dsh-pu-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
386
438
  .dsh-pu-desc { font-size: 11px; color: var(--dsw-alias-label-tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
439
+ .dsh-pu-note { font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
440
+ .dsh-pu-note.is-err { color: var(--dsw-alias-state-error-primary); }
387
441
  .dsh-pu-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; color: var(--dsw-alias-label-secondary); }
388
442
  .dsh-pu-tag { font-size: 10px; line-height: 1; padding: 3px 6px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l1); color: var(--dsw-alias-label-secondary); font-weight: 500; white-space: nowrap; }
389
443
  .dsh-pu-tag.is-new { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent); }
@@ -412,7 +466,10 @@ window.__ModuleLoader__.load({
412
466
  .dsh-pu-job-head { display: flex; align-items: center; gap: 10px; font-size: 12px; }
413
467
  .dsh-pu-job-head .dsh-pu-grow { flex: 1; min-width: 0; }
414
468
  .dsh-pu-bar { height: 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-3); overflow: hidden; }
415
- .dsh-pu-bar div { height: 100%; border-radius: 999px; background: var(--dsw-alias-brand-primary); transition: width .25s ease; }
469
+ .dsh-pu-bar-inner { height: 100%; border-radius: 999px; background: var(--dsw-alias-brand-primary); transition: width .25s ease; }
470
+ .dsh-pu-bar-inner.is-busy { width: 40%; transition: none; animation: dsh-pu-indeterminate 1.4s ease-in-out infinite; }
471
+ @keyframes dsh-pu-indeterminate { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } }
472
+ .dsh-pu-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary); }
416
473
  .dsh-pu-log { max-height: 160px; overflow: auto; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 10.5px; line-height: 1.55; color: var(--dsw-alias-label-secondary); white-space: pre-wrap; word-break: break-word; border-top: 1px dashed var(--dsw-alias-border-l1); padding-top: 8px; margin: 0; }
417
474
  .dsh-pu-banner { display: flex; align-items: center; gap: 8px; font-size: 11.5px; padding: 7px 10px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary); }
418
475
  .dsh-pu-banner.is-warn { border-color: var(--dsw-alias-state-warn-primary); color: var(--dsw-alias-state-warn-primary); }
@@ -420,10 +477,23 @@ window.__ModuleLoader__.load({
420
477
  .dsh-pu-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary); flex-wrap: wrap; }
421
478
  .dsh-pu-spin { width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--dsw-alias-border-l2); border-top-color: var(--dsw-alias-brand-primary); animation: dsh-pu-spin .8s linear infinite; flex: none; }
422
479
  @keyframes dsh-pu-spin { to { transform: rotate(360deg); } }
423
- @media (prefers-reduced-motion: reduce) { .dsh-pu-spin { animation-duration: 2.4s; } }
480
+ @media (prefers-reduced-motion: reduce) { .dsh-pu-spin { animation-duration: 2.4s; } .dsh-pu-bar-inner.is-busy { animation-duration: 3s; } }
424
481
  `;
425
482
 
426
483
  // src/client/components/JobPanel.ts
484
+ var CLOCK_MS = 1e3;
485
+ var STALE_SNAPSHOT_MS = 20 * 1e3;
486
+ function useNow(running, from) {
487
+ const React = getReact();
488
+ const [now, setNow] = React.useState(() => Date.now());
489
+ React.useEffect(() => {
490
+ if (!running || from === void 0) return void 0;
491
+ setNow(Date.now());
492
+ const timer = setInterval(() => setNow(Date.now()), CLOCK_MS);
493
+ return () => clearInterval(timer);
494
+ }, [running, from]);
495
+ return now;
496
+ }
427
497
  function JobPanel(props) {
428
498
  const h = getH();
429
499
  const React = getReact();
@@ -434,6 +504,16 @@ window.__ModuleLoader__.load({
434
504
  const percent = total === 0 ? 0 : Math.round(done / total * 100);
435
505
  const running = job.status === "running" || job.status === "queued";
436
506
  const failed = job.failed ?? 0;
507
+ const inStep = running && job.current !== void 0;
508
+ const now = useNow(inStep, job.stepStartedAt);
509
+ const stamp = `${job.id}|${job.stepStartedAt ?? ""}|${job.done ?? 0}|${job.status}`;
510
+ const lastChange = React.useRef({ stamp, at: Date.now() });
511
+ if (lastChange.current.stamp !== stamp) lastChange.current = { stamp, at: Date.now() };
512
+ const stale = inStep && Date.now() - lastChange.current.at > STALE_SNAPSHOT_MS;
513
+ const stepMs = job.stepStartedAt === void 0 ? 0 : Math.max(0, now - job.stepStartedAt);
514
+ const phaseKey = job.phase === "installing" ? "phaseInstalling" : job.phase === "applying" ? "phaseApplying" : job.phase === "cancelling" ? "phaseCancelling" : "phaseWaiting";
515
+ const phaseText = job.attempt !== void 0 && job.attempt.total > 1 ? t("registryAttempt", job.attempt.index, job.attempt.total) : t(phaseKey);
516
+ const headline = running ? t("progress", done, total) : job.status === "cancelled" ? t("jobCancelled") : t("jobDone");
437
517
  return h(
438
518
  "div",
439
519
  { className: "dsh-pu-job" },
@@ -444,8 +524,17 @@ window.__ModuleLoader__.load({
444
524
  h(
445
525
  "span",
446
526
  { className: "dsh-pu-grow" },
447
- running ? t("progress", done, total) : job.status === "cancelled" ? t("jobCancelled") : t("jobDone"),
448
- job.current === void 0 ? null : h("span", { className: "dsh-pu-mono" }, ` ${job.current}`)
527
+ headline,
528
+ job.current === void 0 ? null : h("span", { className: "dsh-pu-mono" }, ` ${job.current}`),
529
+ inStep ? h(
530
+ "span",
531
+ { className: "dsh-pu-dim" },
532
+ ` \xB7 ${phaseText} \xB7 ${t("elapsed", formatElapsed(stepMs))}`
533
+ ) : null,
534
+ // Once the snapshot stops moving the clock is reporting a number nobody
535
+ // is updating, so say so instead of letting it climb.
536
+ stale ? h("span", { className: "dsh-pu-dim" }, ` \xB7 ${t("staleSnapshot")}`) : null,
537
+ job.abandoned === true ? h("span", { className: "dsh-pu-dim" }, ` \xB7 ${t("stalled")}`) : null
449
538
  ),
450
539
  failed > 0 ? h("span", { className: "dsh-pu-tag is-err" }, t("failedSome", failed)) : null,
451
540
  running ? h(
@@ -459,7 +548,12 @@ window.__ModuleLoader__.load({
459
548
  showLog ? t("hideLog") : t("showLog")
460
549
  )
461
550
  ),
462
- h("div", { className: "dsh-pu-bar" }, h("div", { style: { width: `${percent}%` } })),
551
+ h(
552
+ "div",
553
+ { className: "dsh-pu-bar" },
554
+ inStep ? h("div", { className: "dsh-pu-bar-inner is-busy" }) : h("div", { className: "dsh-pu-bar-inner", style: { width: `${percent}%` } })
555
+ ),
556
+ inStep ? h("div", { className: "dsh-pu-hint" }, t("slowHint")) : null,
463
557
  showLog && Array.isArray(job.log) ? h("pre", { className: "dsh-pu-log" }, job.log.join("\n")) : null
464
558
  );
465
559
  }
@@ -508,7 +602,13 @@ window.__ModuleLoader__.load({
508
602
  disabled: busy || version.current === true,
509
603
  onClick: () => onSwitch(row, version.version)
510
604
  },
511
- version.version === row.latest ? t("back") : t("switchTo")
605
+ /*
606
+ * "Back to latest" is only meaningful when the row is not already on it.
607
+ * The label used to key on `version.version === row.latest` alone, so the
608
+ * version the user is running offered to switch to itself whenever the
609
+ * host had not marked it current.
610
+ */
611
+ version.version === row.latest && version.current !== true ? t("back") : t("switchTo")
512
612
  )
513
613
  );
514
614
  }
@@ -518,12 +618,17 @@ window.__ModuleLoader__.load({
518
618
  const h = getH();
519
619
  const React = getReact();
520
620
  const { row, t, busy, picked, onToggle, onUpgrade, onSwitch, onApprove, onOpenPage } = props;
621
+ const { self, onInstallSelf } = props;
622
+ const { onRetryHistory } = props;
521
623
  const [open, setOpen] = React.useState(false);
522
624
  const versions = Array.isArray(row.versions) ? row.versions : [];
523
625
  const hasHistory = versions.length > 0;
524
- const selectable = row.upgradable === true;
626
+ const selfUpdate = self === true && row.latest !== void 0 && row.latest !== row.version;
627
+ const selectable = row.upgradable === true && self !== true;
525
628
  const status = row.error !== void 0 && row.error !== null ? "err" : row.upgradable ? "new" : "idle";
526
- const versionNote = row.versionError !== void 0 ? row.versionError === "not a profile-managed package" ? t("sourcePath") : row.versionError : void 0;
629
+ const failure = row.versionError !== void 0 && row.versionError !== "" ? row.versionError : void 0;
630
+ const retriable = failure !== void 0 && /unreachable|timeout|registry \d|github/i.test(failure);
631
+ const versionNote = failure === void 0 ? void 0 : failure === "not a profile-managed package" ? t("sourcePath") : failure;
527
632
  const pendingBuilds = Array.isArray(row.pendingBuilds) ? row.pendingBuilds : [];
528
633
  return h(
529
634
  "div",
@@ -566,7 +671,22 @@ window.__ModuleLoader__.load({
566
671
  "div",
567
672
  { className: "dsh-pu-desc" },
568
673
  row.description !== void 0 && row.description !== "" ? row.description : versionNote ?? row.name
569
- )
674
+ ),
675
+ failure !== void 0 && retriable ? h(
676
+ "div",
677
+ { className: "dsh-pu-note is-err", title: failure },
678
+ `${t("historyFailed")}: ${failure}`,
679
+ typeof onRetryHistory === "function" ? h(
680
+ "button",
681
+ {
682
+ type: "button",
683
+ className: "dsh-pu-btn is-ghost is-tiny",
684
+ disabled: busy,
685
+ onClick: onRetryHistory
686
+ },
687
+ t("retry")
688
+ ) : null
689
+ ) : null
570
690
  ),
571
691
  h(
572
692
  "div",
@@ -587,6 +707,16 @@ window.__ModuleLoader__.load({
587
707
  onClick: () => onUpgrade(row)
588
708
  },
589
709
  t("upgrade")
710
+ ) : selfUpdate && typeof onInstallSelf === "function" ? h(
711
+ "button",
712
+ {
713
+ type: "button",
714
+ className: "dsh-pu-btn is-primary is-tiny",
715
+ disabled: busy,
716
+ title: `${t("selfUpdateHint")} \u2192${row.latest}`,
717
+ onClick: () => onInstallSelf(row, row.latest)
718
+ },
719
+ t("selfUpdate")
590
720
  ) : null,
591
721
  typeof row.repoUrl === "string" ? h(
592
722
  "button",
@@ -654,6 +784,8 @@ window.__ModuleLoader__.load({
654
784
  }
655
785
 
656
786
  // src/client/components/Tab.ts
787
+ var SELF_NAME = "dsh-plugin-upgrader";
788
+ var APPEARANCE_RELOAD_DELAY_MS = 1200;
657
789
  function Tab(props) {
658
790
  const h = getH();
659
791
  const React = getReact();
@@ -675,14 +807,27 @@ window.__ModuleLoader__.load({
675
807
  setBusyName(void 0);
676
808
  setCancelling(false);
677
809
  reload(false);
810
+ if (settled === void 0) {
811
+ setJob(void 0);
812
+ setBanner({ tone: "warn", text: t("lostJob") });
813
+ return settled;
814
+ }
678
815
  const failures = Array.isArray(settled?.results) ? settled.results.filter((result) => result.ok !== true) : [];
679
816
  if (failures.length > 0) {
680
817
  setBanner({
681
818
  tone: "err",
682
- text: failures.map((failure) => `${failure.name}: ${failure.message ?? failure.detail ?? "error"}`).join(" \xB7 ")
819
+ text: failures.map((failure) => {
820
+ const code = failure.message ?? "error";
821
+ return failure.detail === void 0 ? `${failure.name}: ${code}` : `${failure.name}: ${failure.detail} (${code})`;
822
+ }).join(" \xB7 ")
683
823
  });
684
824
  } else if (settled?.results?.some((result) => result.application === "restart-required") === true) {
685
825
  setBanner({ tone: "warn", text: t("restart") });
826
+ } else if (replacedAppearance(settled?.results)) {
827
+ setBanner({ tone: "info", text: t("reloadForAppearance") });
828
+ window.setTimeout(() => {
829
+ window.location.reload();
830
+ }, APPEARANCE_RELOAD_DELAY_MS);
686
831
  }
687
832
  return settled;
688
833
  },
@@ -703,31 +848,23 @@ window.__ModuleLoader__.load({
703
848
  if (steps.length === 0) return;
704
849
  setBanner(void 0);
705
850
  setBusyName(label);
851
+ setJob({ id: "", status: "queued", total: steps.length, done: 0, failed: 0, log: [] });
706
852
  const outcome = await submitJob(steps, watch);
707
853
  if (!alive.current) return;
708
- if (outcome.kind === "started") {
709
- setJob({
710
- id: outcome.id,
711
- status: "queued",
712
- total: steps.length,
713
- done: 0,
714
- failed: 0,
715
- log: []
716
- });
717
- return;
718
- }
719
- if (outcome.kind === "adopted") {
720
- setBanner({ tone: "warn", text: t("busyAdopted") });
854
+ if (outcome.kind === "started" || outcome.kind === "adopted") {
855
+ if (outcome.kind === "adopted") setBanner({ tone: "warn", text: t("busyAdopted") });
721
856
  return;
722
857
  }
723
858
  setBusyName(void 0);
859
+ setJob(void 0);
724
860
  const failure = outcome.error;
725
861
  setBanner({ tone: "err", text: failure instanceof Error ? failure.message : String(failure) });
726
862
  },
727
863
  [alive, t, watch]
728
864
  );
729
865
  const rows = Array.isArray(data?.bundles) ? data.bundles : [];
730
- const upgradable = rows.filter((row) => row.upgradable === true);
866
+ const batchable = (row) => row.upgradable === true && row.name !== SELF_NAME;
867
+ const upgradable = rows.filter(batchable);
731
868
  const needle = query.trim().toLowerCase();
732
869
  const filtered = needle === "" ? rows : rows.filter(
733
870
  (row) => row.name.toLowerCase().includes(needle) || String(row.title ?? "").toLowerCase().includes(needle)
@@ -751,6 +888,18 @@ window.__ModuleLoader__.load({
751
888
  const pendingBuilds = Array.isArray(row.pendingBuilds) ? row.pendingBuilds : [];
752
889
  void run([stepOf(row, row.latest ?? row.version ?? "", "upgrade", pendingBuilds)], row.name);
753
890
  };
891
+ const installSelf = React.useCallback(
892
+ (row, version) => {
893
+ const install = props.install;
894
+ if (typeof install !== "function") {
895
+ setBanner({ tone: "warn", text: t("selfUpdateUnavailable") });
896
+ return;
897
+ }
898
+ setBanner(void 0);
899
+ install({ spec: `${row.name}@${version}`, title: row.title, version });
900
+ },
901
+ [props.install, t]
902
+ );
754
903
  const upgradeAll = () => {
755
904
  void run(
756
905
  upgradable.map((row) => stepOf(row, row.latest ?? row.version ?? "", "upgrade")),
@@ -908,7 +1057,10 @@ window.__ModuleLoader__.load({
908
1057
  onUpgrade: upgradeOne,
909
1058
  onSwitch: switchOne,
910
1059
  onApprove: approveAndRetry,
911
- onOpenPage: typeof props.openPackage === "function" ? props.openPackage : void 0
1060
+ onOpenPage: typeof props.openPackage === "function" ? props.openPackage : void 0,
1061
+ self: row.name === SELF_NAME,
1062
+ onInstallSelf: installSelf,
1063
+ onRetryHistory: () => reload(true)
912
1064
  })
913
1065
  )
914
1066
  ),
@@ -10,6 +10,15 @@
10
10
  import type { ChangeResult, PluginManager, StepRecord } from './manager.ts';
11
11
  /** Progress sink for one job. */
12
12
  export type LogSink = (message: string) => void;
13
+ /**
14
+ * Called with the id of the installation currently in flight.
15
+ *
16
+ * `runStep` mints the id it hands the manager, and cancellation needs exactly
17
+ * that id: the manager answers `not-running` for anything else. Reporting it
18
+ * here lets the job record stay cancellable across the whole step, including
19
+ * the removal a version replacement starts with.
20
+ */
21
+ export type RequestIdSink = (requestId: string) => void;
13
22
  /** One step's outcome, including every failure stage. */
14
23
  export interface StepOutcome {
15
24
  name: string;
@@ -58,9 +67,10 @@ export declare function restoreEnabledState(manager: PluginManager, step: Pick<S
58
67
  * @param manager - the plugin manager.
59
68
  * @param step - the requested change.
60
69
  * @param log - progress sink.
70
+ * @param announce - receives the request id the step installs under.
61
71
  * @returns the outcome record.
62
72
  */
63
- export declare function runStep(manager: PluginManager, step: StepRecord, log: LogSink): Promise<StepOutcome>;
73
+ export declare function runStep(manager: PluginManager, step: StepRecord, log: LogSink, announce?: RequestIdSink): Promise<StepOutcome>;
64
74
  /**
65
75
  * Whether a step names something this plugin is willing to install.
66
76
  *
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/host/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE3E,iCAAiC;AACjC,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;AAE/C,yDAAyD;AACzD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,EAAE,EAAE,OAAO,CAAA;IACX,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,WAAW,CAAC,EAAE,YAAY,CAAC,aAAa,CAAC,CAAA;IACzC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;IACvC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,aAAa,CAAC,EAAE;QACd,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,gCAAgC;AAChC,eAAO,MAAM,OAAO,aAAyC,CAAA;AAE7D;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,OAAO,GAAG,SAAS,EAC3B,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAuB9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,WAAW,CAAC,CA4FtB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAM9D"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/host/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAc,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEvF,iCAAiC;AACjC,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;AAE/C;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;AAEvD,yDAAyD;AACzD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,EAAE,EAAE,OAAO,CAAA;IACX,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,WAAW,CAAC,EAAE,YAAY,CAAC,aAAa,CAAC,CAAA;IACzC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;IACvC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,aAAa,CAAC,EAAE;QACd,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,gCAAgC;AAChC,eAAO,MAAM,OAAO,aAAyC,CAAA;AA6K7D;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,OAAO,GAAG,SAAS,EAC3B,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAuB9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,OAAO,EACZ,QAAQ,CAAC,EAAE,aAAa,GACvB,OAAO,CAAC,WAAW,CAAC,CAgHtB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAM9D"}