dsh-plugin-update 0.5.8 → 0.7.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.
@@ -20,11 +20,12 @@
20
20
  * - 截断数值不动(100 节/每类 200 条/单条 500 字/全文 64K),解析附计数元数据只记数字;
21
21
  * 超限类标计数小字,Security 超限进内折叠;BREAKING 前缀识别 + 行首徽标;yanked 横幅只看 to 版。
22
22
  */
23
+ import { type AppLang } from './lang.js';
23
24
  /** 包内日志文件名(包根,与 tarball 内同名文件同一名字)。 */
24
25
  export declare const CHANGELOG_FILENAME = "CHANGELOG.md";
25
- /** 缺日志时的中性提示(不挡安装,安装按钮状态不变)。 */
26
+ /** 缺日志时的中性提示(不挡安装,安装按钮状态不变;zh 原文,渲染走字典 changelog.neutral.hint,留常量只为兼容既有导入)。 */
26
27
  export declare const CHANGELOG_NEUTRAL_HINT = "\u4F5C\u8005\u672A\u63D0\u4F9B\u66F4\u65B0\u8BF4\u660E";
27
- /** 中性提示完整行(含“安装不受影响”,把“不挡安装”说成人话)。 */
28
+ /** 中性提示完整行(含“安装不受影响”,把“不挡安装”说成人话;渲染走字典 changelog.neutral.line)。 */
28
29
  export declare const CHANGELOG_NEUTRAL_LINE = "\u4F5C\u8005\u672A\u63D0\u4F9B\u66F4\u65B0\u8BF4\u660E\uFF0C\u5B89\u88C5\u4E0D\u53D7\u5F71\u54CD\u3002";
29
30
  /** 输入上限(字符):超限截断后解析,永不抛错、不OOM。 */
30
31
  export declare const CHANGELOG_MAX_CHARS: number;
@@ -79,8 +80,12 @@ export declare function matchVersionHeading(line: unknown): string | null;
79
80
  export declare function matchCategoryHeading(line: unknown): string | null;
80
81
  export declare function matchBulletBody(line: unknown): string | null;
81
82
  export declare function isFenceToggle(line: unknown): boolean;
82
- /** 破坏标记徽标文案(中文 UI 一致,原文前缀另行加粗保留)。 */
83
+ /** 破坏标记徽标文案(中文 UI 一致,原文前缀另行加粗保留;渲染走字典 changelog.breaking.badge/aria,留常量只为兼容既有导入)。 */
83
84
  export declare const BREAKING_BADGE_TEXT = "\u4E0D\u517C\u5BB9";
85
+ /** 六类标题按当前语言单语组装(#66,唯一源仍是 CATEGORY_ZH,不另起第二套映射):中文「新增(Added)」观感、英文「Added」。 */
86
+ export declare function changelogCategoryLabel(cat: ChangelogCategory, lang?: AppLang | string | null): string;
87
+ /** 折叠类标题(含条数):中文「弃用预告(Deprecated)(3)」/英文「Deprecated (3)」;条数是变量值,不译。 */
88
+ export declare function changelogFoldedLabel(cat: ChangelogCategory, count: number, lang?: AppLang | string | null): string;
84
89
  /** 剥行首 markdown 装饰(加粗/引用/前后空白),只为识别,渲染保留原文一字不动。 */
85
90
  export declare function stripBreakingDecorations(s: unknown): string;
86
91
  /** 条目是否为破坏标记(去饰后开头匹配,大小写不敏感,中英文冒号皆可)。 */
@@ -122,16 +127,16 @@ export declare function selectChangelogEntries(entries: unknown, fromExclusive:
122
127
  export declare function changelogForUpdate(entries: unknown, runningVersion: unknown, latestVersion: unknown, installedVersion?: unknown): ChangelogEntry[];
123
128
  /** 计数元数据取值(缺省视为与 sections 等长,只记数字)。 */
124
129
  export declare function countsOf(entry: ChangelogEntry | null | undefined): Record<ChangelogCategory, number>;
125
- /** 超限小字(诚实截断):共 M 条,仅显示前 N 条。 */
126
- export declare function truncatedNoteHTML(total: number, shown: number): string;
127
- /** 撤回横幅(只看 to 版,纯展示,不挡安装,文案冻结)。 */
128
- export declare function yankedBannerHTML(version: unknown): string;
129
- /** 单条目 HTML(含破坏标记徽标与前缀加粗,正文一字不动)。 */
130
- export declare function renderChangelogItem(item: string): string;
131
- /** 单节 HTML(调用方保证已过滤 Unreleased 与空节,本函数再兜底一次)。 */
132
- export declare function renderChangelogSection(entry: ChangelogEntry): string;
133
- /** 中性提示 HTML(缺日志、取不到、无区间内容时统一用它,不挡安装)。 */
134
- export declare function renderChangelogNeutral(): string;
130
+ /** 超限小字(诚实截断,#66 入字典单语,lang 缺省 zh 零回归):中文与旧文案一字不差,英文走字典。 */
131
+ export declare function truncatedNoteHTML(total: number, shown: number, lang?: AppLang | string | null): string;
132
+ /** 撤回横幅(只看 to 版,纯展示,不挡安装,#66 入字典单语,lang 缺省 zh 零回归;中文与旧文案一字不差)。 */
133
+ export declare function yankedBannerHTML(version: unknown, lang?: AppLang | string | null): string;
134
+ /** 单条目 HTML(含破坏标记徽标与前缀加粗,正文一字不动;#66 徽标与无障碍名入字典单语,lang 缺省 zh 零回归)。 */
135
+ export declare function renderChangelogItem(item: string, lang?: AppLang | string | null): string;
136
+ /** 单节 HTML(调用方保证已过滤 Unreleased 与空节,本函数再兜底一次;#66 按 lang 单语,缺省 zh 零回归)。 */
137
+ export declare function renderChangelogSection(entry: ChangelogEntry, lang?: AppLang | string | null): string;
138
+ /** 中性提示 HTML(缺日志、取不到、无区间内容时统一用它,不挡安装;#66 入字典单语,lang 缺省 zh 零回归)。 */
139
+ export declare function renderChangelogNeutral(lang?: AppLang | string | null): string;
135
140
  /**
136
141
  * 区间渲染 HTML(纯函数):
137
142
  * entries 非数组/空、无可见区间内容一律回中性提示;
@@ -140,6 +145,7 @@ export declare function renderChangelogNeutral(): string;
140
145
  export declare function renderChangelogHTML(entries: unknown, opts?: {
141
146
  from?: string | null;
142
147
  to?: string | null;
148
+ lang?: AppLang | string | null;
143
149
  }): string;
144
150
  /** 校验诊断(一行一码,行号为原文件 1-based,诊断按行号排序)。 */
145
151
  export interface ChangelogDiagnostic {
package/dist/changelog.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/changelog.ts
2
2
  import { compareReleaseVersions, validReleaseVersion } from "./service.js";
3
+ import { copyText } from "./bilingual.js";
4
+ import { normalizeLangTag } from "./lang.js";
3
5
  const CHANGELOG_FILENAME = "CHANGELOG.md";
4
6
  const CHANGELOG_NEUTRAL_HINT = "\u4F5C\u8005\u672A\u63D0\u4F9B\u66F4\u65B0\u8BF4\u660E";
5
7
  const CHANGELOG_NEUTRAL_LINE = "\u4F5C\u8005\u672A\u63D0\u4F9B\u66F4\u65B0\u8BF4\u660E\uFF0C\u5B89\u88C5\u4E0D\u53D7\u5F71\u54CD\u3002";
@@ -109,6 +111,18 @@ function isFenceToggle(line) {
109
111
  }
110
112
  }
111
113
  const BREAKING_BADGE_TEXT = "\u4E0D\u517C\u5BB9";
114
+ function changelogCategoryLabel(cat, lang) {
115
+ const l = normalizeLangTag(lang ?? "zh");
116
+ const zh = CHANGELOG_CATEGORY_ZH[cat];
117
+ if (l === "en") return cat;
118
+ return zh + "\uFF08" + cat + "\uFF09";
119
+ }
120
+ function changelogFoldedLabel(cat, count, lang) {
121
+ const l = normalizeLangTag(lang ?? "zh");
122
+ const n = String(Math.floor(count));
123
+ if (l === "en") return cat + " (" + n + ")";
124
+ return CHANGELOG_CATEGORY_ZH[cat] + "\uFF08" + cat + "\uFF09\uFF08" + n + "\uFF09";
125
+ }
112
126
  function stripBreakingDecorations(s) {
113
127
  try {
114
128
  let t = String(s !== null && s !== void 0 ? s : "").trim();
@@ -331,26 +345,31 @@ function countsOf(entry) {
331
345
  return { Added: 0, Fixed: 0, Changed: 0, Deprecated: 0, Removed: 0, Security: 0 };
332
346
  }
333
347
  }
334
- function truncatedNoteHTML(total, shown) {
348
+ function truncatedNoteHTML(total, shown, lang) {
335
349
  try {
350
+ const l = normalizeLangTag(lang ?? "zh");
336
351
  const m = Math.floor(total);
337
352
  const n = Math.floor(shown);
338
- return '<div class="dsh-upd-changelog-count">\u5171 ' + String(m) + " \u6761\uFF0C\u4EC5\u663E\u793A\u524D " + String(n) + " \u6761</div>";
353
+ const text = copyText("changelog.truncated.count", l, { m: String(m), n: String(n) });
354
+ return '<div class="dsh-upd-changelog-count">' + escapeChangelogHtml(text) + "</div>";
339
355
  } catch {
340
356
  return "";
341
357
  }
342
358
  }
343
- function yankedBannerHTML(version) {
359
+ function yankedBannerHTML(version, lang) {
344
360
  try {
361
+ const l = normalizeLangTag(lang ?? "zh");
345
362
  const v = typeof version === "string" ? version.trim() : "";
346
363
  if (!v) return "";
347
- return '<div class="dsh-upd-changelog-yanked" role="alert">\u76EE\u6807\u7248\u672C ' + escapeChangelogHtml(v) + " \u5DF2\u88AB\u4F5C\u8005\u64A4\u56DE\uFF08yanked\uFF09\uFF0C\u5B89\u88C5\u4E0D\u53D7\u5F71\u54CD\uFF0C\u7EE7\u7EED\u524D\u8BF7\u786E\u8BA4\u3002</div>";
364
+ const text = copyText("changelog.yanked.banner", l, { version: v });
365
+ return '<div class="dsh-upd-changelog-yanked" role="alert">' + escapeChangelogHtml(text) + "</div>";
348
366
  } catch {
349
367
  return "";
350
368
  }
351
369
  }
352
- function renderChangelogItem(item) {
370
+ function renderChangelogItem(item, lang) {
353
371
  try {
372
+ const l = normalizeLangTag(lang ?? "zh");
354
373
  const s = String(item !== null && item !== void 0 ? item : "");
355
374
  if (!s) return "";
356
375
  const split = splitBreakingPrefix(s);
@@ -358,22 +377,25 @@ function renderChangelogItem(item) {
358
377
  const head = escapeChangelogHtml(split.head);
359
378
  const prefix = escapeChangelogHtml(split.prefix);
360
379
  const rest = escapeChangelogHtml(split.rest);
361
- const badge = '<span class="dsh-upd-breaking-badge" role="img" aria-label="\u7834\u574F\u6027\u53D8\u66F4">' + BREAKING_BADGE_TEXT + "</span> ";
380
+ const badgeText = copyText("changelog.breaking.badge", l);
381
+ const badgeAria = copyText("changelog.breaking.aria", l);
382
+ const badge = '<span class="dsh-upd-breaking-badge" role="img" aria-label="' + escapeChangelogHtml(badgeAria) + '">' + escapeChangelogHtml(badgeText) + "</span> ";
362
383
  return "<li>" + head + badge + "<strong>" + prefix + "</strong>" + rest + "</li>";
363
384
  } catch {
364
385
  return "";
365
386
  }
366
387
  }
367
- function renderChangelogSection(entry) {
388
+ function renderChangelogSection(entry, lang) {
368
389
  try {
369
390
  if (!entry || typeof entry !== "object") return "";
370
391
  const version = String(entry.version !== void 0 && entry.version !== null ? String(entry.version) : "").trim();
371
392
  if (!version || isUnreleasedVersion(version)) return "";
372
393
  if (!hasVisibleSections(entry)) return "";
394
+ const l = normalizeLangTag(lang ?? "zh");
373
395
  const dateRaw = entry.date;
374
396
  const date = typeof dateRaw === "string" ? dateRaw : "";
375
397
  const yankedFlag = entry.yanked === true;
376
- const yankedSuffix = yankedFlag ? " \xB7 \u5DF2\u64A4\u56DE" : "";
398
+ const yankedSuffix = yankedFlag ? copyText("changelog.yanked.suffix", l) : "";
377
399
  const title = (date ? version + " \xB7 " + date : version) + yankedSuffix;
378
400
  const counts = countsOf(entry);
379
401
  const parts = [];
@@ -382,42 +404,44 @@ function renderChangelogSection(entry) {
382
404
  for (const cat of CHANGELOG_MUST_SHOW) {
383
405
  const items = Array.isArray(entry.sections ? entry.sections[cat] : null) ? entry.sections[cat] : [];
384
406
  if (!items || items.length === 0) continue;
385
- const label = cat + " \xB7 " + CHANGELOG_CATEGORY_ZH[cat];
407
+ const label = changelogCategoryLabel(cat, l);
386
408
  parts.push('<div class="dsh-upd-changelog-cat" data-cat="' + cat + '">');
387
409
  parts.push('<div class="dsh-upd-changelog-catname">' + escapeChangelogHtml(label) + "</div>");
388
410
  const total = counts[cat];
389
411
  const shown = items.length;
390
412
  if (total > shown) {
391
- parts.push(truncatedNoteHTML(total, shown));
413
+ parts.push(truncatedNoteHTML(total, shown, l));
392
414
  }
393
415
  parts.push("<ul>");
394
416
  for (const item of items) {
395
417
  if (!item) continue;
396
- const li = renderChangelogItem(item);
418
+ const li = renderChangelogItem(item, l);
397
419
  if (li) parts.push(li);
398
420
  }
399
421
  parts.push("</ul>");
400
422
  if (cat === "Security" && total > shown) {
401
423
  const restCount = total - shown;
402
- parts.push('<details class="dsh-upd-changelog-security-more"><summary>\u5176\u4F59 ' + String(restCount) + ' \u6761</summary><div class="dsh-upd-changelog-more-note">\u4E3A\u4FDD\u6301\u9762\u677F\u6027\u80FD\uFF0C\u5176\u4F59\u6761\u76EE\u5DF2\u6298\u53E0\uFF0C\u53EF\u67E5\u770B\u539F\u6587\u3002</div></details>');
424
+ const sumText = copyText("changelog.security.summary", l, { n: String(restCount) });
425
+ const noteText = copyText("changelog.security.note", l);
426
+ parts.push('<details class="dsh-upd-changelog-security-more"><summary>' + escapeChangelogHtml(sumText) + '</summary><div class="dsh-upd-changelog-more-note">' + escapeChangelogHtml(noteText) + "</div></details>");
403
427
  }
404
428
  parts.push("</div>");
405
429
  }
406
430
  for (const cat of CHANGELOG_FOLDED) {
407
431
  const items = Array.isArray(entry.sections ? entry.sections[cat] : null) ? entry.sections[cat] : [];
408
432
  if (!items || items.length === 0) continue;
409
- const label = cat + " \xB7 " + CHANGELOG_CATEGORY_ZH[cat] + "\uFF08" + String(items.length) + "\uFF09";
433
+ const label = changelogFoldedLabel(cat, items.length, l);
410
434
  parts.push('<details class="dsh-upd-changelog-fold" data-cat="' + cat + '">');
411
435
  parts.push("<summary>" + escapeChangelogHtml(label) + "</summary>");
412
436
  const total = counts[cat];
413
437
  const shown = items.length;
414
438
  if (total > shown) {
415
- parts.push(truncatedNoteHTML(total, shown));
439
+ parts.push(truncatedNoteHTML(total, shown, l));
416
440
  }
417
441
  parts.push("<ul>");
418
442
  for (const item of items) {
419
443
  if (!item) continue;
420
- const li = renderChangelogItem(item);
444
+ const li = renderChangelogItem(item, l);
421
445
  if (li) parts.push(li);
422
446
  }
423
447
  parts.push("</ul></details>");
@@ -428,12 +452,19 @@ function renderChangelogSection(entry) {
428
452
  return "";
429
453
  }
430
454
  }
431
- function renderChangelogNeutral() {
432
- return `<div class="dsh-upd-changelog-neutral">${escapeChangelogHtml(CHANGELOG_NEUTRAL_LINE)}</div>`;
455
+ function renderChangelogNeutral(lang) {
456
+ try {
457
+ const l = normalizeLangTag(lang ?? "zh");
458
+ const text = copyText("changelog.neutral.line", l);
459
+ return `<div class="dsh-upd-changelog-neutral">${escapeChangelogHtml(text)}</div>`;
460
+ } catch {
461
+ return "";
462
+ }
433
463
  }
434
464
  function renderChangelogHTML(entries, opts) {
435
465
  try {
436
- if (!Array.isArray(entries) || entries.length === 0) return renderChangelogNeutral();
466
+ const l = normalizeLangTag(opts?.lang ?? "zh");
467
+ if (!Array.isArray(entries) || entries.length === 0) return renderChangelogNeutral(l);
437
468
  const from = opts && typeof opts.from === "string" ? opts.from : null;
438
469
  const to = opts && typeof opts.to === "string" ? opts.to : null;
439
470
  let ranged;
@@ -449,14 +480,19 @@ function renderChangelogHTML(entries, opts) {
449
480
  }
450
481
  });
451
482
  }
452
- if (ranged.length === 0) return renderChangelogNeutral();
453
- const blocks = ranged.map((e) => renderChangelogSection(e)).filter((s) => !!s);
454
- if (blocks.length === 0) return renderChangelogNeutral();
483
+ if (ranged.length === 0) return renderChangelogNeutral(l);
484
+ const blocks = ranged.map((e) => renderChangelogSection(e, l)).filter((s) => !!s);
485
+ if (blocks.length === 0) return renderChangelogNeutral(l);
455
486
  return `<div class="dsh-upd-changelog">
456
487
  ${blocks.join("\n")}
457
488
  </div>`;
458
489
  } catch {
459
- return renderChangelogNeutral();
490
+ try {
491
+ const ll = normalizeLangTag(opts?.lang ?? "zh");
492
+ return renderChangelogNeutral(ll);
493
+ } catch {
494
+ return "";
495
+ }
460
496
  }
461
497
  }
462
498
  const CHANGELOG_VALIDATE_HINTS = {
@@ -639,6 +675,8 @@ export {
639
675
  RE_VERSION_HEADING,
640
676
  RE_VERSION_NUM,
641
677
  RE_YANKED,
678
+ changelogCategoryLabel,
679
+ changelogFoldedLabel,
642
680
  changelogForUpdate,
643
681
  countsOf,
644
682
  hasVisibleSections,
package/dist/diag.d.ts CHANGED
@@ -31,6 +31,7 @@
31
31
  * 构造全程 best-effort:任何一步异常即回 undefined(调用方按无 diag 的旧形状返回,
32
32
  * 新面板×旧载荷仍为正常缺省,不抛)。
33
33
  */
34
+ import { type AppLang } from './lang.js';
34
35
  /** diag 对象版本:只管大门,新增可选键不 bump,面板永不分支于版本。 */
35
36
  export declare const DIAG_VERSION = 1;
36
37
  /** 阶段枚举(冻结 6 值,与真实接缝对齐)。 */
@@ -67,6 +68,8 @@ export interface DiagInput {
67
68
  environmentKind?: unknown;
68
69
  /** 本次电话耗时毫秒(只收有限非负数)。 */
69
70
  latencyMs?: unknown;
71
+ /** 人话兜底渲染语言(#66 单语:缺省 zh 零回归;电话侧无 DOM 信号时仍落 zh,面板侧 free-text 英文混入属正常见 #57 v2 非目标)。 */
72
+ lang?: AppLang | string | null;
70
73
  }
71
74
  export interface DiagObject {
72
75
  v: number;
package/dist/diag.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/diag.ts
2
2
  import { DIAG_INLINE_BUDGET_BYTES, diagByteLength, enforceDiagBudget, resolveRegistryHost, sanitizeDetail } from "./redaction.js";
3
3
  import { validReleaseVersion, validRequestId } from "./service.js";
4
+ import { copyText } from "./bilingual.js";
5
+ import { normalizeLangTag } from "./lang.js";
4
6
  const DIAG_VERSION = 1;
5
7
  const DIAG_STAGES = [
6
8
  "read-installed",
@@ -116,7 +118,7 @@ function pickEnvironmentKind(raw) {
116
118
  function pickId(raw) {
117
119
  return typeof raw === "string" && validRequestId(raw) ? raw.trim() : null;
118
120
  }
119
- function pickDetail(errorCode, error, httpStatus, phoneKind) {
121
+ function pickDetail(errorCode, error, httpStatus, phoneKind, lang) {
120
122
  try {
121
123
  const rawDetail = error?.detail;
122
124
  if (typeof rawDetail === "string" && rawDetail.trim()) {
@@ -132,24 +134,34 @@ function pickDetail(errorCode, error, httpStatus, phoneKind) {
132
134
  }
133
135
  const code = String(errorCode || "");
134
136
  const phone = String(phoneKind || "");
135
- let fallback = "\u64CD\u4F5C\u5931\u8D25";
136
- if (code === "check-failed" && phone === "update-status") fallback = "\u8BFB\u672C\u5730\u72B6\u6001\u6CA1\u6210\u529F";
137
- else if ((code === "check-failed" || code === "internal") && phone === "update-install") fallback = "\u88C5\u524D\u91CD\u9A8C\u53D6\u6570\u6CA1\u6210\u529F";
138
- else if (code === "check-failed" && httpStatus === 429) fallback = "\u6E90\u8FD4\u56DE 429\uFF0C\u8FD9\u4E00\u5206\u949F\u8BF7\u6C42\u592A\u591A";
139
- else if (code === "check-failed" && typeof httpStatus === "number") fallback = `\u6E90\u8FD4\u56DE ${httpStatus}\uFF0C\u91CD\u8BD5\u4ECD\u5931\u8D25`;
140
- else if (code === "check-failed" || code === "internal") fallback = "\u67E5\u65B0\u7248\u6CA1\u6210\u529F\uFF08\u8054\u7F51\u3001\u6E90\u3001\u9650\u6D41\u90FD\u53EF\u80FD\uFF09";
141
- else if (code === "invalid-release") fallback = "\u6E05\u5355\u91CC\u7684\u7248\u672C\u53F7\u4E0D\u662F\u5408\u6CD5\u7248\u672C";
142
- else if (code === "check-expired") fallback = "\u51ED\u8BC1\u8FC7\u671F\u4E86\uFF0C\u5B89\u88C5\u8BF7\u6C42\u88AB\u62D2";
143
- else if (code === "update-busy") fallback = "\u540C\u4E00\u4F7F\u7528\u8303\u56F4\u6B63\u5728\u88C5\u53E6\u4E00\u4E2A";
144
- else if (code === "install-failed") fallback = "\u5B89\u88C5\u5931\u8D25";
145
- else if (code === "unknown-profile") fallback = "\u4F7F\u7528\u8303\u56F4\u6216\u63D2\u4EF6\u4F4D\u7F6E\u8BA4\u4E0D\u51FA";
146
- else if (code === "source-install") fallback = "\u5F53\u524D\u662F\u4ECE\u6E90\u7801\u88C5\u7684\uFF0C\u4E0D\u662F\u6309\u7248\u672C\u53F7\u88C5\u7684";
147
- else if (code === "invalid-installation") fallback = "\u5DF2\u88C5\u7684\u5305\u4E0D\u5B8C\u6574";
148
- else if (code === "installation-changed") fallback = "\u5B89\u88C5\u4F4D\u7F6E\u5728\u4F7F\u7528\u4E2D\u9014\u53D8\u4E86";
149
- else if (code === "pending-restart") fallback = "\u65B0\u7248\u5DF2\u88C5\u5230\u78C1\u76D8\uFF0C\u6B63\u5728\u8DD1\u7684\u8FD8\u662F\u65E7\u7248";
150
- else if (code === "incompatible-node") fallback = "\u65B0\u7248\u8981\u6C42\u7684 Node \u4E0E\u5F53\u524D\u8FD0\u884C\u7684\u5BF9\u4E0D\u4E0A";
151
- else if (code === "registry-conflict") fallback = "\u672C\u5730\u58F0\u660E\u7684\u7248\u672C\u4E0E\u78C1\u76D8\u5B9E\u9645\u7248\u672C\u4E92\u76F8\u77DB\u76FE";
152
- else if (code === "recovery-required") fallback = "\u4E0A\u6B21\u5B89\u88C5\u88AB\u6253\u65AD\uFF0C\u7559\u4E0B\u4E00\u4E2A\u534A\u622A\u4EFB\u52A1";
137
+ const l = normalizeLangTag(lang ?? "zh");
138
+ let key = "diag.fallback.generic";
139
+ let values;
140
+ if (code === "check-failed" && phone === "update-status") key = "diag.fallback.read-installed";
141
+ else if ((code === "check-failed" || code === "internal") && phone === "update-install") key = "diag.fallback.revalidate-fetch";
142
+ else if (code === "check-failed" && httpStatus === 429) key = "diag.fallback.rate-limited";
143
+ else if (code === "check-failed" && typeof httpStatus === "number") {
144
+ key = "diag.fallback.http-status";
145
+ values = { status: String(httpStatus) };
146
+ } else if (code === "check-failed" || code === "internal") key = "panel.failure.check-failed.title";
147
+ else if (code === "invalid-release") key = "diag.fallback.invalid-release";
148
+ else if (code === "check-expired") key = "panel.failure.check-expired.title";
149
+ else if (code === "update-busy") key = "panel.failure.update-busy.title";
150
+ else if (code === "install-failed") key = "diag.fallback.install-failed";
151
+ else if (code === "unknown-profile") key = "diag.fallback.unknown-profile";
152
+ else if (code === "source-install") key = "diag.fallback.source-install";
153
+ else if (code === "invalid-installation") key = "diag.fallback.invalid-installation";
154
+ else if (code === "installation-changed") key = "diag.fallback.installation-changed";
155
+ else if (code === "pending-restart") key = "diag.fallback.pending-restart";
156
+ else if (code === "incompatible-node") key = "diag.fallback.incompatible-node";
157
+ else if (code === "registry-conflict") key = "diag.fallback.registry-conflict";
158
+ else if (code === "recovery-required") key = "diag.fallback.recovery-required";
159
+ let fallback;
160
+ try {
161
+ fallback = copyText(key, l, values);
162
+ } catch {
163
+ fallback = copyText("diag.fallback.generic", "zh");
164
+ }
153
165
  const clean = sanitizeDetail(fallback);
154
166
  return clean ? clean : null;
155
167
  } catch {
@@ -176,7 +188,7 @@ function buildDiag(input) {
176
188
  const httpStatus = pickHttpStatus(stage, input?.error);
177
189
  const exitCode = pickExitCode(stage, input?.error);
178
190
  const latencyMs = pickLatencyMs(input?.latencyMs);
179
- const detail = pickDetail(errorCode, input?.error, httpStatus, phoneKind);
191
+ const detail = pickDetail(errorCode, input?.error, httpStatus, phoneKind, input?.lang ?? "zh");
180
192
  const targetPackageName = pickPackageName(input?.targetPackageName);
181
193
  const runningVersion = pickVersion(input?.runningVersion);
182
194
  const latestVersion = pickVersion(input?.latestVersion);
@@ -1,7 +1,7 @@
1
1
  import { type BilingualKey } from './bilingual.js';
2
2
  import { type AppLang, type LocaleOption } from './lang.js';
3
3
  import { type BatchRowView } from './panel-batch.js';
4
- import { type UpdatePanelTheme } from './panel.js';
4
+ import { type UpdatePanelTheme, type UpdateThemeTokens } from './panel.js';
5
5
  /** 批量入口件的形态(与单入口同一套取值)。 */
6
6
  export type BatchEntryVariant = 'button' | 'badge' | 'inline';
7
7
  /** 什么时候主动查一次(只读;与单入口同一口径)。 */
@@ -40,10 +40,16 @@ export interface UpdateBatchEntryOptions {
40
40
  /** 调宿主电话:(phoneName, args) => Promise<reply>。 */
41
41
  call: (name: string, args: Record<string, unknown>) => Promise<unknown>;
42
42
  variant?: BatchEntryVariant;
43
- /** 主题:与单入口/面板同一套(default/archive,d5-paper 为旧别名仍可用)。 */
43
+ /** 主题:与单入口/面板同一套(default/archive)。 */
44
44
  theme?: UpdatePanelTheme;
45
+ /** 主题变量覆盖:见 panel 的 UpdateThemeTokens;批量入口根 + 打开的批量面板同步生效,不传即零回归。 */
46
+ themeTokens?: UpdateThemeTokens;
45
47
  /** 缺省 'mount':进页面静默查一次(只调 .batchStatus,只读)。'never' 则只在用户点击时查。 */
46
48
  autoCheck?: BatchEntryAutoCheck;
49
+ /** 打开面板自动查(#59 D3,透传给批量面板):缺省 true;面板按用户偏好 > 本选项 > 缺省,并与入口预查共用知识时间戳去重(一击只查一次)。 */
50
+ checkOnOpen?: boolean;
51
+ /** 自动继续未终态轮次(#59 Q3,透传给批量面板):缺省 true,与显式按钮并存。 */
52
+ autoResume?: boolean;
47
53
  /** 缺省 'always':查完总是开批量面板(见本类型说明)。 */
48
54
  openOn?: BatchEntryOpenOn;
49
55
  /** 覆盖默认按钮文案(不传就用聚合文案)。 */
@@ -78,6 +84,8 @@ export interface UpdateBatchEntryController {
78
84
  /** 当前聚合读数(含七档计数,接入方排版与 onActivate 共用)。 */
79
85
  summary(): BatchEntrySummary;
80
86
  setTheme(theme: UpdatePanelTheme): void;
87
+ /** 换一套主题变量(重绘;打开的批量面板同步;传 undefined 即清掉覆盖)。 */
88
+ setThemeTokens(tokens: UpdateThemeTokens | undefined): void;
81
89
  unmount(): void;
82
90
  }
83
91
  /**
@@ -11,7 +11,8 @@ import {
11
11
  } from "./panel-batch.js";
12
12
  import {
13
13
  failureCodeOf,
14
- normalizePanelTheme
14
+ normalizePanelTheme,
15
+ themeTokensStyleFor
15
16
  } from "./panel.js";
16
17
  function isObject(value) {
17
18
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -145,10 +146,12 @@ function mountUpdateBatchEntry(container, options) {
145
146
  if (openOn !== "has-update" && openOn !== "always" && openOn !== "manual" && openOn !== "direct") {
146
147
  throw new Error("[dsh-plugin-update] \u70B9\u51FB\u53BB\u5411\u975E\u6CD5\uFF1A\u53EA\u6536 has-update / always / manual / direct\uFF08\u6536\u5230 " + JSON.stringify(options.openOn) + "\uFF09");
147
148
  }
148
- if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive" && options.theme !== "d5-paper") {
149
- 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");
149
+ if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive") {
150
+ throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 " + JSON.stringify(options.theme) + "\uFF09");
150
151
  }
151
152
  let theme = normalizePanelTheme(options.theme ?? "default");
153
+ themeTokensStyleFor(options.themeTokens ?? void 0);
154
+ let themeTokens = options.themeTokens;
152
155
  const pollMs = options.pollMs;
153
156
  if (pollMs !== void 0 && (typeof pollMs !== "number" || !Number.isFinite(pollMs) || pollMs < MIN_PANEL_POLL_MS)) {
154
157
  throw new Error("[dsh-plugin-update] \u9762\u677F\u8F6E\u8BE2\u95F4\u9694\u975E\u6CD5\uFF1A\u4E0D\u5F97\u5C0F\u4E8E 250 \u6BEB\u79D2\uFF08\u6536\u5230 " + JSON.stringify(options.pollMs) + "\uFF09");
@@ -220,9 +223,11 @@ function mountUpdateBatchEntry(container, options) {
220
223
  const labelText = labelOverride ?? (activating ? copyText("batch-entry.action.checking", lang) : copyText(batchEntryBilingualKeyFor(stateOf()), lang, batchEntryBilingualValuesFor(stateOf())));
221
224
  const busyAttr = activating ? ' disabled aria-busy="true"' : "";
222
225
  const themeAttr = theme === "archive" ? ' data-theme="archive"' : "";
226
+ const tokensStyle = themeTokensStyleFor(themeTokens ?? void 0);
227
+ const tokensAttr = tokensStyle ? ' style="' + tokensStyle + '"' : "";
223
228
  const control = variant === "badge" ? '<button type="button" class="dsh-upd-entry-dot" ' + ENTRY_ATTR + '="activate" title="' + escapeHtml(labelText) + '" aria-label="' + escapeHtml(labelText) + '"' + busyAttr + "></button>" : '<button type="button" class="dsh-upd-entry-btn" ' + ENTRY_ATTR + '="activate"' + busyAttr + ">" + labelHTML + "</button>";
224
229
  const noteHTML = note ? '<span class="dsh-upd-entry-note" data-dsh-upd-note="1">' + escapeHtml(note) + "</span>" : "";
225
- return "<style>" + UPDATE_ENTRY_CSS + "\n" + BILINGUAL_CSS + '</style>\n<span class="dsh-upd-entry" data-variant="' + variant + '" data-state="' + summary.kind + '"' + themeAttr + ">" + control + noteHTML + "</span>";
230
+ return "<style>" + UPDATE_ENTRY_CSS + "\n" + BILINGUAL_CSS + '</style>\n<span class="dsh-upd-entry" data-variant="' + variant + '" data-state="' + summary.kind + '"' + themeAttr + tokensAttr + ">" + control + noteHTML + "</span>";
226
231
  }
227
232
  function render() {
228
233
  if (!mounted) return;
@@ -267,12 +272,15 @@ function mountUpdateBatchEntry(container, options) {
267
272
  prefix: options.prefix,
268
273
  mode,
269
274
  theme,
275
+ themeTokens,
270
276
  pollMs,
271
277
  titles,
272
278
  onRestartRequested: options.onRestartRequested,
273
279
  copyText: options.copyText,
274
280
  autoChangelog: options.autoChangelog,
275
281
  locale: localeOpt,
282
+ checkOnOpen: options.checkOnOpen,
283
+ autoResume: options.autoResume,
276
284
  // 面板点「关闭」即走入口件的完整关闭(收 dialog + 还原按钮 + 重查一次),不再是面板自己停轮询。
277
285
  onCloseRequested: () => close(),
278
286
  call: panelCall
@@ -337,13 +345,19 @@ function mountUpdateBatchEntry(container, options) {
337
345
  render();
338
346
  }
339
347
  function setTheme(next) {
340
- if (next !== "default" && next !== "archive" && next !== "d5-paper") {
341
- 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");
348
+ if (next !== "default" && next !== "archive") {
349
+ throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 " + JSON.stringify(next) + "\uFF09");
342
350
  }
343
351
  theme = normalizePanelTheme(next);
344
352
  if (panel) void panel.setTheme(theme);
345
353
  else render();
346
354
  }
355
+ function setThemeTokens(next) {
356
+ themeTokensStyleFor(next ?? void 0);
357
+ themeTokens = next ?? void 0;
358
+ if (panel) void panel.setThemeTokens(themeTokens);
359
+ else render();
360
+ }
347
361
  function onClick(ev) {
348
362
  if (!mounted) return;
349
363
  try {
@@ -406,7 +420,7 @@ function mountUpdateBatchEntry(container, options) {
406
420
  if (variant === "inline") mountPanel("embedded");
407
421
  else render();
408
422
  if (autoCheck === "mount") void refresh();
409
- return { refresh, open, close, label: currentLabel, summary: summaryOf, setTheme, unmount };
423
+ return { refresh, open, close, label: currentLabel, summary: summaryOf, setTheme, setThemeTokens, unmount };
410
424
  }
411
425
  export {
412
426
  batchEntryBilingualHTMLFor,
package/dist/entry.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type UpdatePanelContainer, type UpdatePanelTheme } from './panel.js';
1
+ import { type UpdatePanelContainer, type UpdatePanelTheme, type UpdateThemeTokens } from './panel.js';
2
2
  import type { UpdateSnapshot } from './ports.js';
3
3
  import { type BilingualKey } from './bilingual.js';
4
4
  import { type AppLang, type LocaleOption } from './lang.js';
@@ -21,6 +21,24 @@ export interface UpdateEntryState {
21
21
  snapshot: UpdateSnapshot | null;
22
22
  error: string | null;
23
23
  }
24
+ /**
25
+ * 入口件按钮尺寸覆盖(#69):只影响 button 本体(字号/内边距/圆角/整体缩放),不碰 dialog 面板。
26
+ * 缺省(不传)保持现状、零回归;badge 圆点与 inline 内嵌不读这些变量,天然不受影响。
27
+ * 实现口径与现有 `--dsh-update-*` 颜色变量一致:本选项只是把同样的 CSS 变量以内联方式写到容器上,
28
+ * 手写 CSS 变量(`--dsh-update-entry-font-size` / `--dsh-update-entry-padding` /
29
+ * `--dsh-update-entry-border-radius` / `--dsh-update-entry-scale`)同样生效,两者等价。
30
+ * `scale` 即宿主临时方案里容器 `zoom: 1` 开关的正式形态:改一个数即缩放(默认 1)。
31
+ */
32
+ export interface EntrySizing {
33
+ /** 按钮字号(CSS font-size 值,如 '12px'),不传即 13px 默认。 */
34
+ fontSize?: string;
35
+ /** 按钮内边距(CSS padding 值,如 '2px 8px'),不传即 '4px 12px' 默认。 */
36
+ padding?: string;
37
+ /** 按钮圆角(CSS border-radius 值,如 '8px'),不传即默认主题 6px / archive 主题 3px。 */
38
+ borderRadius?: string;
39
+ /** 整体缩放(对应 CSS `zoom`,如 1.2 放大、0.9 缩小),须为大于 0 的有限数,不传即 1。 */
40
+ scale?: number;
41
+ }
24
42
  export interface UpdateEntryOptions {
25
43
  pluginId: string;
26
44
  /** 单插件电话前缀(与宿主侧一致)。 */
@@ -28,7 +46,7 @@ export interface UpdateEntryOptions {
28
46
  /** 调宿主电话:(phoneName, args) => Promise<reply>。 */
29
47
  call: (name: string, args: Record<string, unknown>) => Promise<unknown>;
30
48
  variant?: EntryVariant;
31
- /** 主题:`default` 最小可用深色,`archive` 档案卷纸面浅色(`d5-paper` 为旧别名仍可用)。 */
49
+ /** 主题:与面板同一套(`default` 最小可用,`archive` 档案卷纸面浅色)。 */
32
50
  theme?: UpdatePanelTheme;
33
51
  /** 缺省 'mount':进页面静默查一次(只读)。'never' 则只在用户点击时查。 */
34
52
  autoCheck?: EntryAutoCheck;
@@ -36,6 +54,10 @@ export interface UpdateEntryOptions {
36
54
  openOn?: EntryOpenOn;
37
55
  /** 覆盖默认按钮文案(不传就用状态联动文案)。 */
38
56
  label?: string;
57
+ /** 按钮尺寸覆盖(#69):见 EntrySizing;不传即零回归,badge/inline 不受影响。 */
58
+ sizing?: EntrySizing;
59
+ /** 主题变量覆盖:见 panel 的 UpdateThemeTokens;入口件根 + 打开的 dialog 面板同步生效,不传即零回归。 */
60
+ themeTokens?: UpdateThemeTokens;
39
61
  /** 语言覆盖(#60 v2):'zh' | 'en' | { getActive(): string; subscribe?(cb): () => void },不传即跟随 html[lang] > navigator > zh。 */
40
62
  locale?: LocaleOption;
41
63
  profileName?: string;
@@ -62,6 +84,8 @@ export interface UpdateEntryController {
62
84
  /** 当前按钮上的状态文案(接入方做自定义排版时读它)。 */
63
85
  label(): string;
64
86
  setTheme(theme: UpdatePanelTheme): void;
87
+ /** 换一套主题变量(重绘;打开的 dialog 面板同步;传 undefined 即清掉覆盖)。 */
88
+ setThemeTokens(tokens: UpdateThemeTokens | undefined): void;
65
89
  unmount(): void;
66
90
  }
67
91
  /**
@@ -82,5 +106,10 @@ export declare function entryBilingualHTMLFor(state: UpdateEntryState | null | u
82
106
  export declare function entryBilingualTextFor(state: UpdateEntryState | null | undefined, lang?: AppLang | string | null): string;
83
107
  /** 入口件文案第二出口(唯一出处:测试与接入方都读它;与 HTML 出口同语言,v2 起按 lang 取字典,不再硬编码中文)。 */
84
108
  export declare function entryLabelFor(state: UpdateEntryState | null | undefined, lang?: AppLang | string | null): string;
109
+ /**
110
+ * `sizing` → 容器 style 属性体(纯函数:同一输入永远算出同一串;空/缺省回空串,即不写 style)。
111
+ * 非法即抛(未知键、空串、注入字符、非正有限 scale 都不收)。
112
+ */
113
+ export declare function entrySizingStyleFor(sizing: EntrySizing | null | undefined): string;
85
114
  export declare const UPDATE_ENTRY_CSS: string;
86
115
  export declare function mountUpdateEntry(container: UpdatePanelContainer, options: UpdateEntryOptions): UpdateEntryController;