dsh-plugin-update 0.4.0 → 0.5.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/dist/host.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  } from "./config.js";
12
12
  import { manualCommand } from "./commands.js";
13
13
  import { fetchReleaseChangelogText } from "./changelog-io.js";
14
+ import { shouldFetchChangelog } from "./changelog.js";
14
15
  import { buildDiag } from "./diag.js";
15
16
  import { fetchNpmVersionRelease, isVersionAllowedInChannel, updateError, validReleaseVersion, validRequestId } from "./service.js";
16
17
  import { createUpdateDiskPorts, createUpdateExecutor, createUpdateQueuePorts } from "./store.js";
@@ -73,14 +74,24 @@ import {
73
74
  CHANGELOG_MUST_SHOW,
74
75
  CHANGELOG_NEUTRAL_HINT,
75
76
  CHANGELOG_NEUTRAL_LINE,
77
+ BREAKING_BADGE_TEXT,
78
+ CHANGELOG_POLL_BACKOFF_MS,
76
79
  changelogForUpdate,
80
+ countsOf,
77
81
  hasVisibleSections,
82
+ isBreakingChangelogItem,
78
83
  isUnreleasedVersion,
79
84
  parseChangelog,
80
85
  renderChangelogHTML,
86
+ renderChangelogItem,
81
87
  renderChangelogNeutral,
82
88
  renderChangelogSection,
83
- selectChangelogEntries
89
+ selectChangelogEntries,
90
+ shouldFetchChangelog as shouldFetchChangelog2,
91
+ splitBreakingPrefix,
92
+ truncatedNoteHTML,
93
+ validateChangelog,
94
+ yankedBannerHTML
84
95
  } from "./changelog.js";
85
96
  import {
86
97
  CHANGELOG_DEFAULT_REGISTRY,
@@ -578,12 +589,24 @@ function createHostUpdate(deps = {}, configInput) {
578
589
  const readerOverrides = { ...deps.readerOverrides ?? {}, ctx, ...deps.pluginManager ? { pluginManager: deps.pluginManager } : {} };
579
590
  const phoneNames = buildPhoneNames(config.prefix);
580
591
  const changelogCache = /* @__PURE__ */ new Map();
592
+ const changelogFailedAt = /* @__PURE__ */ new Map();
581
593
  async function readChangelog(args) {
582
594
  const raw = args ? args["version"] : void 0;
583
595
  if (!validReleaseVersion(raw)) throw updateError("invalid-release");
584
596
  const version = raw;
585
597
  if (!isVersionAllowedInChannel(version, config.releaseChannel)) throw updateError("invalid-release");
586
598
  if (changelogCache.has(version)) return { version, markdown: changelogCache.get(version) ?? null };
599
+ try {
600
+ let nowMs = 0;
601
+ try {
602
+ nowMs = Date.now();
603
+ } catch {
604
+ nowMs = 0;
605
+ }
606
+ const failedAt = changelogFailedAt.has(version) ? changelogFailedAt.get(version) : null;
607
+ void shouldFetchChangelog({ hasCache: false, failedAt, now: nowMs, isManual: true });
608
+ } catch {
609
+ }
587
610
  const fetchImpl = readerOverrides.fetchImpl ?? globalThis.fetch;
588
611
  const release = await fetchNpmVersionRelease(fetchImpl, config.checkTimeoutMs, {
589
612
  targetPackageName: config.targetPackageName,
@@ -591,12 +614,29 @@ function createHostUpdate(deps = {}, configInput) {
591
614
  releaseChannel: config.releaseChannel,
592
615
  version
593
616
  });
594
- const markdown = await fetchReleaseChangelogText(
595
- fetchImpl,
596
- { tarball: release.tarball, integrity: release.integrity, version },
597
- { targetPackageName: config.targetPackageName, registryUrl: config.registryUrl, timeoutMs: config.checkTimeoutMs }
598
- );
617
+ let markdown;
618
+ try {
619
+ markdown = await fetchReleaseChangelogText(
620
+ fetchImpl,
621
+ { tarball: release.tarball, integrity: release.integrity, version },
622
+ { targetPackageName: config.targetPackageName, registryUrl: config.registryUrl, timeoutMs: config.checkTimeoutMs }
623
+ );
624
+ } catch (err) {
625
+ try {
626
+ changelogFailedAt.set(version, Date.now());
627
+ } catch {
628
+ try {
629
+ changelogFailedAt.set(version, 0);
630
+ } catch {
631
+ }
632
+ }
633
+ throw err;
634
+ }
599
635
  changelogCache.set(version, markdown);
636
+ try {
637
+ changelogFailedAt.delete(version);
638
+ } catch {
639
+ }
600
640
  return { version, markdown };
601
641
  }
602
642
  async function readStatus(args) {
@@ -713,6 +753,7 @@ function __resetSharedUpdateReaderForTests() {
713
753
  sharedDesktopPnpm = null;
714
754
  }
715
755
  export {
756
+ BREAKING_BADGE_TEXT,
716
757
  CHANGELOG_ALL_CATEGORIES,
717
758
  CHANGELOG_DEFAULT_REGISTRY,
718
759
  CHANGELOG_FETCH_MAX_BYTES,
@@ -725,6 +766,7 @@ export {
725
766
  CHANGELOG_MUST_SHOW,
726
767
  CHANGELOG_NEUTRAL_HINT,
727
768
  CHANGELOG_NEUTRAL_LINE,
769
+ CHANGELOG_POLL_BACKOFF_MS,
728
770
  CHANGELOG_TARBALL_MAX_BYTES,
729
771
  CHANGELOG_TAR_MAX_BYTES,
730
772
  COPY_BUDGET_CHARS,
@@ -764,6 +806,7 @@ export {
764
806
  compareReleaseVersions,
765
807
  compareVersions,
766
808
  containingPackage,
809
+ countsOf,
767
810
  createHostUpdate,
768
811
  createSkipDiskPorts,
769
812
  createUpdateDiskPorts2 as createUpdateDiskPorts,
@@ -782,6 +825,7 @@ export {
782
825
  extractChangelogFromTar,
783
826
  fetchReleaseChangelogText2 as fetchReleaseChangelogText,
784
827
  hasVisibleSections,
828
+ isBreakingChangelogItem,
785
829
  isHeadOfQueue2 as isHeadOfQueue,
786
830
  isPrereleaseVersion,
787
831
  isUnreleasedVersion,
@@ -800,6 +844,7 @@ export {
800
844
  registrySpec,
801
845
  releaseOwnerInQueue2 as releaseOwnerInQueue,
802
846
  renderChangelogHTML,
847
+ renderChangelogItem,
803
848
  renderChangelogNeutral,
804
849
  renderChangelogSection,
805
850
  resolveCliEntry,
@@ -810,9 +855,14 @@ export {
810
855
  sanitizeForCopy,
811
856
  selectChangelogEntries,
812
857
  setOwnerIfFree2 as setOwnerIfFree,
858
+ shouldFetchChangelog2 as shouldFetchChangelog,
813
859
  skipKey,
860
+ splitBreakingPrefix,
814
861
  truncateToWordBoundary,
862
+ truncatedNoteHTML,
815
863
  validReleaseVersion2 as validReleaseVersion,
816
864
  validVersion,
817
- visibleQueueFor2 as visibleQueueFor
865
+ validateChangelog,
866
+ visibleQueueFor2 as visibleQueueFor,
867
+ yankedBannerHTML
818
868
  };
@@ -1,6 +1,7 @@
1
1
  // AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/panel-batch.ts
2
2
  import { assertPrefix, MIN_PANEL_POLL_MS } from "./config.js";
3
3
  import { validReleaseVersion } from "./service.js";
4
+ import { shouldFetchChangelog } from "./changelog.js";
4
5
  import {
5
6
  isTerminalPhase,
6
7
  normalizeBatchSession
@@ -12,6 +13,7 @@ import {
12
13
  createBrowserSkipStore,
13
14
  failureCopy,
14
15
  isKnownFailureCode,
16
+ normalizePanelTheme,
15
17
  renderUpdatePanelHTML
16
18
  } from "./panel.js";
17
19
  const BATCH_PANEL_POLL = {
@@ -92,7 +94,7 @@ function batchRowStatus(row, skippedVersion) {
92
94
  }
93
95
  function renderBatchPanelHTML(input) {
94
96
  const rows = Array.isArray(input.rows) ? input.rows : [];
95
- const theme = input.theme === "d5-paper" ? "d5-paper" : "default";
97
+ const theme = normalizePanelTheme(input.theme);
96
98
  const mode = input.mode === "dialog" ? "dialog" : "embedded";
97
99
  const titles = input.titles && typeof input.titles === "object" ? input.titles : {};
98
100
  const skippedVersions = input.skippedVersions && typeof input.skippedVersions === "object" ? input.skippedVersions : {};
@@ -167,7 +169,7 @@ function renderBatchPanelHTML(input) {
167
169
  parts.push('<div class="dsh-upd-batch-notice" role="status">' + escapeHtml(notice) + "</div>");
168
170
  }
169
171
  const kernel = parts.join(String.fromCharCode(10));
170
- const d5 = theme === "d5-paper";
172
+ const d5 = normalizePanelTheme(theme) === "d5-paper";
171
173
  const attr = d5 ? ' data-theme="d5-paper"' : "";
172
174
  const seal = batchSealOf(counts);
173
175
  const root = '<div class="dsh-upd dsh-upd-batch" data-mode="' + mode + '" data-seal="' + escapeHtml(seal.text) + '" data-seal-tone="' + seal.tone + '"' + attr + ">\n" + kernel + "\n</div>";
@@ -618,10 +620,10 @@ function mountUpdateBatchPanel(container, options) {
618
620
  if (mode !== "embedded" && mode !== "dialog") {
619
621
  throw new Error("[dsh-plugin-update] \u6446\u653E\u5F62\u6001\u975E\u6CD5\uFF1A\u53EA\u6536 embedded \u6216 dialog\uFF08\u6536\u5230 " + JSON.stringify(options.mode) + "\uFF09");
620
622
  }
621
- let theme = options.theme === void 0 ? "default" : options.theme;
622
- if (theme !== "default" && theme !== "d5-paper") {
623
- throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 " + JSON.stringify(options.theme) + "\uFF09");
623
+ if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive" && options.theme !== "d5-paper") {
624
+ throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08d5-paper \u4E3A\u65E7\u522B\u540D\u4ECD\u53EF\u7528\uFF09\uFF08\u6536\u5230 " + JSON.stringify(options.theme) + "\uFF09");
624
625
  }
626
+ let theme = normalizePanelTheme(options.theme ?? "default");
625
627
  const call = options.call;
626
628
  const titles = options.titles && typeof options.titles === "object" ? options.titles : {};
627
629
  const onRestartRequested = options.onRestartRequested;
@@ -630,7 +632,7 @@ function mountUpdateBatchPanel(container, options) {
630
632
  const autoChangelogEnabled = options.autoChangelog !== false;
631
633
  const rowChangelog = /* @__PURE__ */ new Map();
632
634
  const rowInflight = /* @__PURE__ */ new Set();
633
- const rowFailed = /* @__PURE__ */ new Map();
635
+ const rowFailedAt = /* @__PURE__ */ new Map();
634
636
  let rowAutoSeq = 0;
635
637
  let rows = [];
636
638
  let expandedKey = null;
@@ -671,7 +673,19 @@ function mountUpdateBatchPanel(container, options) {
671
673
  const cached = rowChangelog.get(key);
672
674
  if (cached && cached.version === version) return;
673
675
  if (rowInflight.has(key)) return;
674
- if (rowFailed.get(key) === version) return;
676
+ try {
677
+ const failKey = key + "\0" + version;
678
+ const hasCache = false;
679
+ const failedAt = rowFailedAt.has(failKey) ? rowFailedAt.get(failKey) : null;
680
+ let nowMs = 0;
681
+ try {
682
+ nowMs = Date.now();
683
+ } catch {
684
+ nowMs = 0;
685
+ }
686
+ if (!shouldFetchChangelog({ hasCache, failedAt, now: nowMs, isManual: false })) return;
687
+ } catch {
688
+ }
675
689
  const phone2 = changelogPhoneOf(row);
676
690
  if (!phone2) return;
677
691
  rowInflight.add(key);
@@ -681,7 +695,14 @@ function mountUpdateBatchPanel(container, options) {
681
695
  rowInflight.delete(key);
682
696
  if (!mounted || seq !== rowAutoSeq) return;
683
697
  if (!isObject(reply) || reply["ok"] !== true) {
684
- rowFailed.set(key, version);
698
+ try {
699
+ rowFailedAt.set(key + "\0" + version, Date.now());
700
+ } catch {
701
+ try {
702
+ rowFailedAt.set(key + "\0" + version, 0);
703
+ } catch {
704
+ }
705
+ }
685
706
  return;
686
707
  }
687
708
  const md = reply["markdown"];
@@ -690,13 +711,23 @@ function mountUpdateBatchPanel(container, options) {
690
711
  },
691
712
  () => {
692
713
  rowInflight.delete(key);
693
- if (mounted && seq === rowAutoSeq) rowFailed.set(key, version);
714
+ if (mounted && seq === rowAutoSeq) {
715
+ try {
716
+ rowFailedAt.set(key + "\0" + version, Date.now());
717
+ } catch {
718
+ try {
719
+ rowFailedAt.set(key + "\0" + version, 0);
720
+ } catch {
721
+ }
722
+ }
723
+ }
694
724
  }
695
725
  );
696
726
  }
727
+ let lastHTML = "";
697
728
  function render() {
698
729
  if (!mounted) return;
699
- container.innerHTML = renderBatchPanelHTML({
730
+ const nextHTML = renderBatchPanelHTML({
700
731
  rows,
701
732
  theme,
702
733
  mode,
@@ -711,6 +742,10 @@ function mountUpdateBatchPanel(container, options) {
711
742
  confirmCancel: confirmCancelArmed,
712
743
  changelogs: changelogView()
713
744
  });
745
+ if (nextHTML !== lastHTML) {
746
+ lastHTML = nextHTML;
747
+ container.innerHTML = nextHTML;
748
+ }
714
749
  }
715
750
  function rowOfKey(key) {
716
751
  return rows.find((row) => row.key === key) ?? null;
@@ -802,7 +837,7 @@ function mountUpdateBatchPanel(container, options) {
802
837
  return;
803
838
  }
804
839
  applyReply(reply);
805
- rowFailed.clear();
840
+ rowFailedAt.clear();
806
841
  } catch {
807
842
  if (!mounted) return;
808
843
  lastError = "check-failed";
package/dist/panel.d.ts CHANGED
@@ -2,8 +2,17 @@ import { type VisibleQueue } from './queue.js';
2
2
  import type { BlockedReason, UpdateSnapshot } from './ports.js';
3
3
  /** 摆放形态:默认内嵌,切弹窗走同一参数。 */
4
4
  export type UpdatePanelMode = 'embedded' | 'dialog';
5
- /** 面板主题:默认最小可用样式;`d5-paper` 为可选 D5 档案卷专业主题(只换肤,不换 DOM 顺序)。 */
6
- export type UpdatePanelTheme = 'default' | 'd5-paper';
5
+ /** 面板主题:默认最小可用样式;`archive` 为档案卷纸面浅色(只换肤,不换 DOM 顺序;`d5-paper` 为旧别名)。 */
6
+ /**
7
+ * 面板主题:`default` 最小可用深色;`archive` 档案卷纸面浅色(原型敲定的案卷风格,只换肤)。
8
+ * `d5-paper` 是 archive 的旧别名(历史取值),仍可用,渲染逐字相同。
9
+ */
10
+ export type UpdatePanelTheme = 'default' | 'archive' | 'd5-paper';
11
+ /**
12
+ * 主题归一:archive 与旧别名 d5-paper 走同一套渲染(DOM 属性仍为 d5-paper,既有覆盖样式不断);
13
+ * 其余一律回 default(纯渲染函数永不抛;挂载/setTheme 的非法值另行抛错)。
14
+ */
15
+ export declare function normalizePanelTheme(value: unknown): 'default' | 'd5-paper';
7
16
  /** 与宿主通话的传输函数:面板只认这个签名,不认任何宿主对象的具体形状。 */
8
17
  export type UpdatePanelCall = (phoneName: string, args: Record<string, unknown>) => Promise<Record<string, unknown>>;
9
18
  /** 复制文本的出口:不传即尝试浏览器剪贴板,都没有则只留“已生成、请手动复制”提示。 */
@@ -17,7 +26,7 @@ export interface UpdatePanelOptions {
17
26
  prefix?: string;
18
27
  /** 摆放形态:默认内嵌。 */
19
28
  mode?: UpdatePanelMode;
20
- /** 面板主题:默认 `default`(最小可用样式,一字不动);传 `d5-paper` 切 D5 档案卷可选主题。 */
29
+ /** 面板主题:默认 `default`(最小可用样式,一字不动);传 `archive` 切档案卷(`d5-paper` 为旧别名)。 */
21
30
  theme?: UpdatePanelTheme;
22
31
  /** 是否看他人排队明细:默认只看自己的(他人仅露正忙占位,位置照给)。 */
23
32
  showOthers?: boolean;
@@ -52,6 +61,11 @@ export interface UpdatePanelOptions {
52
61
  * 回来经内部通道换日志节;显式传过 changelogMarkdown 或传 false 即退回手动模式。
53
62
  */
54
63
  autoChangelog?: boolean;
64
+ /**
65
+ * 弹窗关闭的落地:只在 dialog 下点「关闭」/按 Esc 时调用;调用方在此撤掉弹窗 DOM。
66
+ * 不传则回退为只停轮询(unmount),DOM 留给调用方处理。入口件打开的 dialog 已内置(收 dialog + 还原按钮)。
67
+ */
68
+ onCloseRequested?: () => void | Promise<void>;
55
69
  }
56
70
  /** 挂载点:只要有 innerHTML 的容器即可(浏览器元素或测试替身都行)。 */
57
71
  export interface UpdatePanelContainer {
package/dist/panel.js CHANGED
@@ -5,8 +5,13 @@ import { validReleaseVersion } from "./service.js";
5
5
  import {
6
6
  changelogForUpdate,
7
7
  parseChangelog,
8
- renderChangelogHTML
8
+ renderChangelogHTML,
9
+ shouldFetchChangelog,
10
+ yankedBannerHTML
9
11
  } from "./changelog.js";
12
+ function normalizePanelTheme(value) {
13
+ return value === "d5-paper" || value === "archive" ? "d5-paper" : "default";
14
+ }
10
15
  const BLOCKED_COPY = {
11
16
  "unknown-profile": {
12
17
  title: "\u4F7F\u7528\u8303\u56F4\u6216\u63D2\u4EF6\u4F4D\u7F6E\u8BA4\u4E0D\u51FA",
@@ -513,6 +518,12 @@ const UPDATE_PANEL_CSS = [
513
518
  ".dsh-upd-changelog li{margin:2px 0}",
514
519
  ".dsh-upd-changelog-fold{margin:4px 0}",
515
520
  ".dsh-upd-changelog-fold>summary{cursor:pointer}",
521
+ ".dsh-upd-changelog-count{font-size:12px;opacity:.7;margin:2px 0 4px}",
522
+ ".dsh-upd-changelog-yanked{border-left:4px solid var(--dsh-upd-warn-line,#d97706);background:var(--dsh-upd-warn-bg,#fffbeb);padding:6px 10px;margin:6px 0;font-size:13px}",
523
+ ".dsh-upd-breaking-badge{display:inline-block;font-size:11px;font-weight:700;border:1px solid currentColor;border-radius:3px;padding:0 5px;margin-right:6px;vertical-align:baseline}",
524
+ ".dsh-upd-changelog-security-more{margin:4px 0 6px}",
525
+ ".dsh-upd-changelog-security-more>summary{cursor:pointer;font-size:12px;opacity:.8}",
526
+ ".dsh-upd-changelog-more-note{font-size:12px;opacity:.7;margin:2px 0 4px}",
516
527
  ".dsh-upd-changelog-neutral{color:inherit;opacity:.8}",
517
528
  ".dsh-upd-overlay{position:fixed;inset:0;background:rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;z-index:9999}",
518
529
  ".dsh-upd-overlay .dsh-upd{background:var(--dsh-upd-bg,#ffffff);max-height:85vh;overflow:auto}",
@@ -590,7 +601,8 @@ const UPDATE_PANEL_D5_CSS = [
590
601
  "--d5-shadow:0 1px 2px rgba(60,40,20,.08),0 12px 32px rgba(60,40,20,.10);",
591
602
  "font-family:var(--d5-sans);color:var(--d5-ink);background:var(--d5-card);",
592
603
  "border:1px solid var(--d5-line-strong);border-radius:4px;box-shadow:var(--d5-shadow)}",
593
- '.dsh-upd[data-theme="d5-paper"] button{border-color:var(--d5-line-strong);background:transparent;color:var(--d5-ink);border-radius:3px;font-family:var(--d5-sans)}',
604
+ // 按钮脸自己不透明(宿主底色未知时也读得出;卡片上渲染与 transparent 逐字同色)。
605
+ '.dsh-upd[data-theme="d5-paper"] button{border-color:var(--d5-line-strong);background:var(--d5-card);color:var(--d5-ink);border-radius:3px;font-family:var(--d5-sans)}',
594
606
  '.dsh-upd[data-theme="d5-paper"] button:hover:not(:disabled){border-color:var(--d5-accent);color:var(--d5-accent)}',
595
607
  '.dsh-upd[data-theme="d5-paper"] button[data-primary="1"]{background:var(--d5-accent);border-color:var(--d5-accent);color:#fff}',
596
608
  '.dsh-upd[data-theme="d5-paper"] button[data-primary="1"]:hover:not(:disabled){background:var(--d5-accent-deep);color:#fff}',
@@ -601,6 +613,10 @@ const UPDATE_PANEL_D5_CSS = [
601
613
  '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="restart"]{border-color:var(--d5-warn);background:var(--d5-warn-bg);font-family:var(--d5-serif);border-width:2px}',
602
614
  '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="failed"],.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="blocked"]{border-color:var(--d5-bad);background:var(--d5-bad-bg)}',
603
615
  '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="done"]{border-color:var(--d5-ok);background:var(--d5-ok-bg)}',
616
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-changelog-yanked{border-color:var(--d5-warn);background:var(--d5-warn-bg);color:var(--d5-ink)}',
617
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-breaking-badge{color:var(--d5-accent);border-color:var(--d5-accent)}',
618
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-changelog-count{color:var(--d5-muted)}',
619
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-changelog-more-note{color:var(--d5-muted)}',
604
620
  // —— 大印章(原型 :206 `.seal`:右上 88px、旋转 -7°、双细框;内容与色调来自根属性,不加节点)——
605
621
  '.dsh-upd[data-theme="d5-paper"]{position:relative;padding:22px 26px 20px}',
606
622
  '.dsh-upd[data-theme="d5-paper"]::before{content:attr(data-seal);position:absolute;top:20px;right:24px;width:88px;height:88px;',
@@ -788,7 +804,22 @@ function renderUpdatePanelKernel(input, view) {
788
804
  const fromText = String(snapshot.runningVersion ?? "");
789
805
  const toText = String(snapshot.latestVersion ?? "");
790
806
  const rangeTitle = fromText && toText ? `\u66F4\u65B0\u8BF4\u660E\uFF08${fromText} \u2192 ${toText}\uFF09\uFF1A` : "\u66F4\u65B0\u8BF4\u660E\uFF1A";
791
- inner = `<div class="dsh-upd-changelog-wrap"><div>${escapeHtml(rangeTitle)}</div>
807
+ let yankedBanner = "";
808
+ try {
809
+ const toEntry = Array.isArray(ranged) ? ranged.find(function(e) {
810
+ try {
811
+ return e && e.version === toText;
812
+ } catch {
813
+ return false;
814
+ }
815
+ }) : null;
816
+ if (toEntry && toEntry.yanked === true && toText) {
817
+ yankedBanner = yankedBannerHTML(toText);
818
+ }
819
+ } catch {
820
+ yankedBanner = "";
821
+ }
822
+ inner = `<div class="dsh-upd-changelog-wrap"><div>${escapeHtml(rangeTitle)}</div>${yankedBanner}
792
823
  ${changelogHTML}
793
824
  </div>`;
794
825
  } catch {
@@ -863,7 +894,7 @@ ${changelogHTML}
863
894
  function renderUpdatePanelHTML(input) {
864
895
  const view = panelViewModel(input);
865
896
  const kernel = renderUpdatePanelKernel(input, view);
866
- const d5 = input.theme === "d5-paper";
897
+ const d5 = normalizePanelTheme(input.theme) === "d5-paper";
867
898
  const attr = d5 ? ' data-theme="d5-paper"' : "";
868
899
  const sealAttr = ` data-seal="${escapeHtml(view.seal.text)}" data-seal-tone="${view.seal.tone}"`;
869
900
  const body = input.mode === "dialog" ? `<div class="dsh-upd-overlay" data-mode="dialog"><div class="dsh-upd" data-mode="dialog" data-plugin="${escapeHtml(input.pluginId)}"${sealAttr}${attr}>
@@ -948,7 +979,7 @@ function mountUpdatePanel(container, options) {
948
979
  let manualChangelogOverride = typeof options.changelogMarkdown === "string";
949
980
  const changelogCache = /* @__PURE__ */ new Map();
950
981
  const changelogInflight = /* @__PURE__ */ new Set();
951
- const changelogFailed = /* @__PURE__ */ new Set();
982
+ const changelogFailedAt = /* @__PURE__ */ new Map();
952
983
  let autoSeq = 0;
953
984
  const pollMs = options.pollMs === void 0 ? DEFAULT_PANEL_POLL_MS : options.pollMs;
954
985
  if (typeof pollMs !== "number" || !Number.isFinite(pollMs) || pollMs < MIN_PANEL_POLL_MS) {
@@ -958,13 +989,15 @@ function mountUpdatePanel(container, options) {
958
989
  if (mode !== "embedded" && mode !== "dialog") {
959
990
  throw new Error(`[dsh-plugin-update] \u6446\u653E\u5F62\u6001\u975E\u6CD5\uFF1A\u53EA\u6536 embedded \u6216 dialog\uFF08\u6536\u5230 ${JSON.stringify(options.mode)}\uFF09`);
960
991
  }
961
- let theme = options.theme ?? "default";
962
- if (theme !== "default" && theme !== "d5-paper") {
963
- throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
992
+ if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive" && options.theme !== "d5-paper") {
993
+ throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08d5-paper \u4E3A\u65E7\u522B\u540D\u4ECD\u53EF\u7528\uFF09\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
964
994
  }
995
+ let theme = normalizePanelTheme(options.theme ?? "default");
965
996
  let showOthers = options.showOthers === true;
966
997
  const call = options.call;
967
998
  const onRestartRequested = options.onRestartRequested;
999
+ const onCloseRequested = typeof options.onCloseRequested === "function" ? options.onCloseRequested : null;
1000
+ let lastHTML = "";
968
1001
  const copyText = options.copyText ?? defaultCopyText;
969
1002
  const skipStore = options.skipStore ?? createBrowserSkipStore(pluginId);
970
1003
  const hostKind = typeof options.hostKind === "string" && options.hostKind ? options.hostKind : null;
@@ -1005,7 +1038,7 @@ function mountUpdatePanel(container, options) {
1005
1038
  }
1006
1039
  const latest = snapshot?.latestVersion ?? null;
1007
1040
  const skippedLatest = !!latest && validReleaseVersion(latest) && skipStore.has(latest);
1008
- container.innerHTML = renderUpdatePanelHTML({
1041
+ const nextHTML = renderUpdatePanelHTML({
1009
1042
  snapshot,
1010
1043
  manual,
1011
1044
  queue,
@@ -1023,6 +1056,10 @@ function mountUpdatePanel(container, options) {
1023
1056
  profileName: profileNameOption ?? envProfileName,
1024
1057
  hostKind: hostKind ?? envHostKind
1025
1058
  });
1059
+ if (nextHTML !== lastHTML) {
1060
+ lastHTML = nextHTML;
1061
+ container.innerHTML = nextHTML;
1062
+ }
1026
1063
  }
1027
1064
  function applyStatusReply(reply) {
1028
1065
  if (!isObject(reply)) return;
@@ -1060,7 +1097,16 @@ function mountUpdatePanel(container, options) {
1060
1097
  if (skipStore.has(v)) return null;
1061
1098
  } catch {
1062
1099
  }
1063
- if (changelogCache.has(v) || changelogInflight.has(v) || changelogFailed.has(v)) return null;
1100
+ if (changelogInflight.has(v)) return null;
1101
+ const hasCache = changelogCache.has(v);
1102
+ const failedAt = changelogFailedAt.has(v) ? changelogFailedAt.get(v) : null;
1103
+ let nowMs = 0;
1104
+ try {
1105
+ nowMs = Date.now();
1106
+ } catch {
1107
+ nowMs = 0;
1108
+ }
1109
+ if (!shouldFetchChangelog({ hasCache, failedAt, now: nowMs, isManual: false })) return null;
1064
1110
  return v;
1065
1111
  }
1066
1112
  function maybeAutoChangelog() {
@@ -1073,7 +1119,14 @@ function mountUpdatePanel(container, options) {
1073
1119
  changelogInflight.delete(v);
1074
1120
  if (!mounted || seq !== autoSeq) return;
1075
1121
  if (!reply || typeof reply !== "object" || reply["ok"] !== true) {
1076
- changelogFailed.add(v);
1122
+ try {
1123
+ changelogFailedAt.set(v, Date.now());
1124
+ } catch {
1125
+ try {
1126
+ changelogFailedAt.set(v, 0);
1127
+ } catch {
1128
+ }
1129
+ }
1077
1130
  return;
1078
1131
  }
1079
1132
  const md = reply["markdown"];
@@ -1085,7 +1138,16 @@ function mountUpdatePanel(container, options) {
1085
1138
  },
1086
1139
  () => {
1087
1140
  changelogInflight.delete(v);
1088
- if (mounted && seq === autoSeq) changelogFailed.add(v);
1141
+ if (mounted && seq === autoSeq) {
1142
+ try {
1143
+ changelogFailedAt.set(v, Date.now());
1144
+ } catch {
1145
+ try {
1146
+ changelogFailedAt.set(v, 0);
1147
+ } catch {
1148
+ }
1149
+ }
1150
+ }
1089
1151
  }
1090
1152
  );
1091
1153
  }
@@ -1117,7 +1179,7 @@ function mountUpdatePanel(container, options) {
1117
1179
  const reply = await call(phoneNames.updateCheck, queueArgs());
1118
1180
  if (!mounted) return;
1119
1181
  applyStatusReply(reply);
1120
- changelogFailed.clear();
1182
+ changelogFailedAt.clear();
1121
1183
  } catch {
1122
1184
  if (!mounted) return;
1123
1185
  lastError = "check-failed";
@@ -1256,7 +1318,7 @@ function mountUpdatePanel(container, options) {
1256
1318
  return;
1257
1319
  }
1258
1320
  case "close-view": {
1259
- if (mode === "dialog") unmount();
1321
+ await requestDialogClose();
1260
1322
  return;
1261
1323
  }
1262
1324
  }
@@ -1269,10 +1331,10 @@ function mountUpdatePanel(container, options) {
1269
1331
  render();
1270
1332
  }
1271
1333
  async function setTheme(next) {
1272
- if (next !== "default" && next !== "d5-paper") {
1273
- throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 ${JSON.stringify(next)}\uFF09`);
1334
+ if (next !== "default" && next !== "archive" && next !== "d5-paper") {
1335
+ throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08d5-paper \u4E3A\u65E7\u522B\u540D\u4ECD\u53EF\u7528\uFF09\uFF08\u6536\u5230 ${JSON.stringify(next)}\uFF09`);
1274
1336
  }
1275
- theme = next;
1337
+ theme = normalizePanelTheme(next);
1276
1338
  render();
1277
1339
  }
1278
1340
  async function setShowOthers(show) {
@@ -1289,11 +1351,21 @@ function mountUpdatePanel(container, options) {
1289
1351
  } catch {
1290
1352
  }
1291
1353
  }
1354
+ async function requestDialogClose() {
1355
+ if (!mounted || mode !== "dialog") return;
1356
+ if (onCloseRequested) {
1357
+ try {
1358
+ await onCloseRequested();
1359
+ } catch {
1360
+ }
1361
+ }
1362
+ unmount();
1363
+ }
1292
1364
  function onKeyDown(ev) {
1293
1365
  try {
1294
1366
  const e = ev;
1295
1367
  if (!mounted || mode !== "dialog" || !e || e.key !== "Escape") return;
1296
- unmount();
1368
+ void requestDialogClose();
1297
1369
  } catch {
1298
1370
  }
1299
1371
  }
@@ -1409,6 +1481,7 @@ export {
1409
1481
  isQueueBusy,
1410
1482
  isUnreleasedVersion,
1411
1483
  mountUpdatePanel,
1484
+ normalizePanelTheme,
1412
1485
  normalizeQueueState,
1413
1486
  panelViewModel,
1414
1487
  parseChangelog2 as parseChangelog,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-update",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "可复用的更新系统装成的 npm 包:任何 DSH 插件照文档能集成检查更新等功能(地图 #579)",
5
5
  "license": "MIT",
6
6
  "author": "王辰浩",