dsh-plugin 2.3.2 → 2.3.4

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.
Files changed (84) hide show
  1. package/README.en.md +3 -9
  2. package/README.md +3 -9
  3. package/client/client.js +371 -364
  4. package/client/client.js.map +1 -1
  5. package/lib/http/catalog.js +4 -4
  6. package/lib/http/diagnostics.js +4 -4
  7. package/lib/http/install.js +13 -13
  8. package/lib/http/npm-release-age.js +3 -3
  9. package/lib/http/shared.js +1 -1
  10. package/lib/services/host-version.js +2 -2
  11. package/lib/services/install/dsh-compat.js +1 -1
  12. package/lib/services/install/npm-resolve.js +1 -1
  13. package/lib/services/install/operation-log.js +3 -3
  14. package/lib/services/install/preflight.js +1 -1
  15. package/lib/services/install/registry-fallback.js +3 -3
  16. package/lib/services/install/release-target.js +2 -2
  17. package/lib/services/install/task-queue.js +35 -20
  18. package/lib/services/loader.js +8 -8
  19. package/lib/services/npm-release-age.js +8 -8
  20. package/lib/services/probe.js +12 -12
  21. package/lib/services/profile/installed-versions.js +2 -2
  22. package/lib/services/profile/profile.js +2 -2
  23. package/lib/types/services/host-version.d.ts +1 -1
  24. package/lib/types/services/install/install-types.d.ts +5 -5
  25. package/lib/types/services/install/operation-log.d.ts +1 -1
  26. package/lib/types/services/install/preflight.d.ts +1 -1
  27. package/lib/types/services/install/registry-fallback.d.ts +1 -1
  28. package/lib/types/services/install/release-target.d.ts +1 -1
  29. package/lib/types/services/install/task-queue.d.ts +10 -7
  30. package/lib/types/services/loader.d.ts +5 -5
  31. package/lib/types/services/npm-release-age.d.ts +5 -5
  32. package/lib/types/services/probe.d.ts +4 -4
  33. package/package.json +1 -1
  34. package/src/client/components/PluginHubSection.tsx +29 -29
  35. package/src/client/components/catalog/CatalogList.tsx +1 -1
  36. package/src/client/components/catalog/PluginCard.tsx +3 -3
  37. package/src/client/components/modals/AboutModal.tsx +2 -2
  38. package/src/client/components/modals/ConfirmDialog.tsx +2 -2
  39. package/src/client/components/modals/HubUpdateModal.tsx +3 -3
  40. package/src/client/components/modals/InstalledDetailModal.tsx +1 -1
  41. package/src/client/components/modals/LogsPathModal.tsx +1 -1
  42. package/src/client/components/modals/NotificationsModal.tsx +2 -2
  43. package/src/client/components/modals/modals.tsx +10 -10
  44. package/src/client/components/views/CustomInstallView.tsx +2 -2
  45. package/src/client/components/views/InstalledView.tsx +2 -2
  46. package/src/client/components/views/SettingsView.tsx +3 -3
  47. package/src/client/data/catalog.ts +1 -1
  48. package/src/client/data/host.ts +2 -2
  49. package/src/client/data/hub.ts +3 -3
  50. package/src/client/hooks/useCatalog.ts +14 -14
  51. package/src/client/hooks/useSettings.ts +2 -2
  52. package/src/client/hooks/useTaskQueue.ts +30 -23
  53. package/src/client/locales.ts +15 -15
  54. package/src/client/logic/constants.ts +1 -1
  55. package/src/client/logic/ensure-plugin-css.ts +2 -2
  56. package/src/client/logic/failures.ts +9 -9
  57. package/src/client/logic/install-command.ts +1 -2
  58. package/src/client/logic/installed.ts +1 -1
  59. package/src/client/logic/release-age.ts +10 -10
  60. package/src/client/logic/urls.ts +6 -4
  61. package/src/client/styles/Header.module.css +1 -1
  62. package/src/client/styles/Modal.module.css +2 -2
  63. package/src/client/styles/SectionTabs.module.css +1 -1
  64. package/src/client/styles/tokens.module.css +1 -1
  65. package/src/client/types.ts +4 -4
  66. package/src/server/http/catalog.ts +4 -4
  67. package/src/server/http/diagnostics.ts +4 -4
  68. package/src/server/http/install.ts +13 -13
  69. package/src/server/http/npm-release-age.ts +3 -3
  70. package/src/server/http/shared.ts +1 -1
  71. package/src/server/services/host-version.ts +2 -2
  72. package/src/server/services/install/dsh-compat.ts +1 -1
  73. package/src/server/services/install/install-types.ts +5 -5
  74. package/src/server/services/install/npm-resolve.ts +1 -1
  75. package/src/server/services/install/operation-log.ts +3 -3
  76. package/src/server/services/install/preflight.ts +1 -1
  77. package/src/server/services/install/registry-fallback.ts +3 -3
  78. package/src/server/services/install/release-target.ts +2 -2
  79. package/src/server/services/install/task-queue.ts +40 -26
  80. package/src/server/services/loader.ts +10 -10
  81. package/src/server/services/npm-release-age.ts +10 -10
  82. package/src/server/services/probe.ts +12 -12
  83. package/src/server/services/profile/installed-versions.ts +2 -2
  84. package/src/server/services/profile/profile.ts +2 -2
@@ -59,7 +59,7 @@ export const zh = {
59
59
  npmInstallPlaceholder: 'npm install <包名> 或直接输包名',
60
60
  npmInstallExample: '支持原生命令:npm install lodash、npm i lodash、pnpm add @scope/pkg',
61
61
  npmInstallExample2: '全局安装 CLI:npm install -g @scope/cli;或直接输入包名 lodash',
62
- npmInstallInvalid: '请输入 npm 包名(如 lodash、@scope/pkg),或原生安装命令(如 npm install lodash、npm i lodash、pnpm add @scope/pkg、npm install -g @scope/cli)',
62
+ npmInstallInvalid: '请输入 npm 包名(如 lodash、@scope/pkg),或原生安装命令(如 npm install lodash、npm i lodash、pnpm add @scope/pkg、npm install -g @scope/cli);不支持 GitHub 源码地址,请改用该插件的 npm 包名',
63
63
  installHelp: '帮助',
64
64
  helpModalTitle: '支持的命令格式',
65
65
  npmInstallHelp: 'lodash、@scope/pkg\t直接输入包名\nnpm install lodash\nnpm i lodash\npnpm add @scope/pkg\npnpm i @scope/pkg\nnpm install -g @scope/cli\t全局安装 CLI 工具,不进「已安装」列表',
@@ -257,14 +257,14 @@ export const zh = {
257
257
  requestTimeout: '请求超时:无法访问 GitHub 或网络不稳定,请稍后重试',
258
258
  confirmCancel: '取消',
259
259
  doneBtn: '完成',
260
- // 结果视图兜底:仅当「刷新解决不了」时(热挂载失败 / 非 dsh 插件 / 更新)才出现单个「立即重启」;
260
+ // 结果视图补充:仅当「刷新解决不了」时(热加载失败 / 非 dsh 插件 / 更新)才出现单个「立即重启」;
261
261
  // 关闭弹窗即视为「稍后」,不再单独给「稍后重启」按钮
262
262
  restartNow: '立即重启',
263
263
  restarting: '正在重启…',
264
264
  restartHint: '重启会中断正在进行的安装/卸载任务,其进度将丢失。部分插件(如插件市场、顶层 bundle)需重启后才会生效。',
265
265
  // 桌面端宿主归应用壳管、插件重启不了它:点「立即重启」时改提示用户退出重开
266
266
  toastRestartDesktop: '桌面端的宿主由应用管理,请在应用菜单(或托盘)选择退出后重新打开,重启后生效',
267
- // 装完即生效:插件已热挂进运行中 loader,结果弹窗关闭时自动刷新页面(无需用户点按钮)
267
+ // 装完即生效:插件已热加载进运行中 loader,结果弹窗关闭时自动刷新页面(无需用户点按钮)
268
268
  reloadHint: '插件已加载,关闭本窗口后会自动刷新页面生效。',
269
269
  toastInstalledLive: '安装成功,已立即生效',
270
270
  toastInstalledReload: '安装成功,正在自动刷新页面…',
@@ -311,7 +311,7 @@ export const zh = {
311
311
  // 非插件侧失败(未知原因)改为引导到插件中心仓库排查
312
312
  failHubIssueHint: '此失败已被判定为环境或插件中心自身问题(非插件作者问题),带着错误日志到插件中心仓库一键提交 Issue',
313
313
  failHubIssueBig: '反馈给插件中心排查',
314
- // 未知失败兜底:不归到插件作者,引导到插件中心仓库排查
314
+ // 未知失败补充:不归到插件作者,引导到插件中心仓库排查
315
315
  failUnclassifiedHint: '{action}失败,原因未能自动识别。这通常不是插件自身的问题(可能与本机环境或插件中心有关),请将错误信息反馈给插件中心,由维护者排查。',
316
316
  // 插件中心自身状态类消息(插件未安装 / 不在此处卸载 / 已在队列中):已安装列表与实读快照不同步,非插件问题,不引导提 Issue
317
317
  failHubStateHint: '插件列表状态已过期或与本地不一致:这次{action}的目标可能已经在列表里了(要升级请点「更新」),也可能已经不在已安装列表中,或已在队列中。这不是插件本身的问题 —— 请刷新页面后重试。',
@@ -491,10 +491,10 @@ export const zh = {
491
491
  update: '可更新',
492
492
  updateAvailable: '有更新',
493
493
  updateAvailableHint: '检测到更新:有版本号的仓库比对版本号,无版本号的仓库比对仓库最近更新时间',
494
- // 门禁暂缓:新版本发布未满 24 小时,此时安装会被 pnpm 静默回退(或直接拒绝),
495
- // 给出确切可重试时刻,而不是让徽标静默消失、让用户以为已经是最新
494
+ // 冷却期未到:新版本发布未满 24 小时,此时安装会被 pnpm 不提示地回退(或直接拒绝),
495
+ // 给出确切可重试时刻,而不是让徽标悄悄消失、让用户以为已经是最新
496
496
  updatePending: '暂缓到 {time} 后可更新',
497
- updatePendingHint: '新版本发布未满 24 小时,pnpm 供应链门禁会拦下;到时间后再更新即可',
497
+ updatePendingHint: '新版本发布还不满 24 小时,pnpm 暂时不让安装;到时间后再更新即可',
498
498
  hubUpdateHint: '检测到 DSH Plugin Hub 新版本,点击查看更新内容并更新',
499
499
  hubUpdateTitle: 'DSH Plugin Hub 有新版本',
500
500
  hubUpdateDesc: '点击下方按钮即可更新,完成后需重启宿主生效。',
@@ -508,8 +508,8 @@ export const zh = {
508
508
  hubVersionStateAvailable: '可更新',
509
509
  hubVersionStatePending: '暂缓提示',
510
510
  close: '关闭',
511
- // 暂缓提示:远端确有新版本,但发布未满 24 小时(pnpm 供应链安全门槛),与「已是最新」严格区分
512
- hubPendingDesc: '最新版已发布,但发布尚未满 24 小时:此时更新会被 pnpm 静默回退到旧版本,因此暂不提示。稍后这里会自动出现更新入口。',
511
+ // 暂缓提示:远端确有新版本,但发布未满 24 小时(pnpm 发布冷却期),与「已是最新」严格区分
512
+ hubPendingDesc: '最新版已发布,但发布尚未满 24 小时:此时更新会被 pnpm 不提示地回退到旧版本,因此暂不提示。稍后这里会自动出现更新入口。',
513
513
  // 头部「关注我们」弹窗:平台介绍(内容由 Worker /about Markdown 推送,非写死)
514
514
  followUs: '关注我们',
515
515
  aboutTitle: '关注我们',
@@ -573,7 +573,7 @@ export const en = {
573
573
  npmInstallPlaceholder: 'npm install <pkg> or a bare package name',
574
574
  npmInstallExample: 'Native commands: npm install lodash, npm i lodash, pnpm add @scope/pkg',
575
575
  npmInstallExample2: 'Global CLI: npm install -g @scope/cli; or just type a package name like lodash',
576
- npmInstallInvalid: 'Enter an npm package name (e.g. lodash, @scope/pkg), or a native install command (e.g. npm install lodash, npm i lodash, pnpm add @scope/pkg, npm install -g @scope/cli)',
576
+ npmInstallInvalid: 'Enter an npm package name (e.g. lodash, @scope/pkg), or a native install command (e.g. npm install lodash, npm i lodash, pnpm add @scope/pkg, npm install -g @scope/cli); GitHub source URLs are not supported — use the plugin\'s npm package name instead',
577
577
  installHelp: 'Help',
578
578
  helpModalTitle: 'Supported command formats',
579
579
  npmInstallHelp: 'lodash or @scope/pkg\tjust type the package name\nnpm install lodash\nnpm i lodash\npnpm add @scope/pkg\npnpm i @scope/pkg\nnpm install -g @scope/cli\tinstalls the CLI globally, not shown in Installed',
@@ -700,7 +700,7 @@ export const en = {
700
700
  settingsDiagFail: 'Unreachable',
701
701
  // 系统诊断通道列表:一键全检 + 每通道一行,点击重测
702
702
  diagNpm: 'npm registry',
703
- // 诊断 npm 行:未配置镜像时显示的兜底文案(探测仍打官方源)
703
+ // 诊断 npm 行:未配置镜像时显示的备用文案(探测仍打官方源)
704
704
  diagNpmUnset: 'Unset (follow local npm config)',
705
705
  diagGithub: 'GitHub',
706
706
  diagCatalog: 'Catalog site',
@@ -771,14 +771,14 @@ export const en = {
771
771
  requestTimeout: 'Request timed out: GitHub may be unreachable or the network is unstable — try again later',
772
772
  confirmCancel: 'Cancel',
773
773
  doneBtn: 'Done',
774
- // 结果视图兜底:仅刷新解决不了时(热挂载失败 / 非 dsh 插件 / 更新)才给单个「立即重启」;
774
+ // 结果视图补充:仅刷新解决不了时(热加载失败 / 非 dsh 插件 / 更新)才给单个「立即重启」;
775
775
  // 关闭弹窗即视为「稍后」
776
776
  restartNow: 'Restart now',
777
777
  restarting: 'Restarting…',
778
778
  restartHint: 'Restarting interrupts any in-progress install / uninstall task — its progress will be lost. Some plugins (such as plugin markets / top-level bundles) only take effect after a restart.',
779
779
  // 桌面端:宿主由应用壳托管,插件重启不了它 —— 提示用户退出后重开
780
780
  toastRestartDesktop: 'In the desktop app the host is managed by the app: quit from the app menu (or tray) and open it again — the change takes effect after that restart',
781
- // 免重启即生效:安装已热挂载进运行中 loader;关闭弹窗时页面自动刷新(不给手动刷新按钮)
781
+ // 免重启即生效:安装已热加载进运行中 loader;关闭弹窗时页面自动刷新(不给手动刷新按钮)
782
782
  reloadHint: 'The plugin is loaded — closing this window will refresh the page automatically.',
783
783
  toastInstalledLive: 'Installed — active now',
784
784
  toastInstalledReload: 'Installed — refreshing the page automatically…',
@@ -823,7 +823,7 @@ export const en = {
823
823
  // 非插件侧失败(未分类)改为投递到插件中心仓库分诊
824
824
  failHubIssueHint: 'This failure was classified as an environment or Plugin Hub issue (not a plugin-author issue). Open a pre-filled issue on the Plugin Hub repo with this error log.',
825
825
  failHubIssueBig: 'Report to the Plugin Hub for triage',
826
- // 未知失败兜底:不投插件作者,改投插件中心仓库分诊
826
+ // 未知失败补充:不投插件作者,改投插件中心仓库分诊
827
827
  failUnclassifiedHint: 'The {action} failed and the cause could not be identified automatically. This is usually not the plugin itself (it may relate to your local environment or the Plugin Hub). Please report the error to the Plugin Hub so the maintainers can triage it.',
828
828
  // 插件中心自身的状态守卫消息(未安装 / 不在此处卸载 / 已在队列中 / 已安装):已装列表与实读快照不同步 —— 不是插件问题,不引导提 Issue
829
829
  failHubStateHint: 'The plugin list is out of date or out of sync: the target of this {action} may already be in the list (use "Update" to upgrade), may no longer be in the installed list, or may already be queued. This is not a plugin problem — refresh the page and retry.',
@@ -1017,7 +1017,7 @@ export const en = {
1017
1017
  hubVersionStateAvailable: 'Update available',
1018
1018
  hubVersionStatePending: 'Held for 24h',
1019
1019
  close: 'Close',
1020
- // 暂缓提示:确有新版本,但仍在 pnpm 24h 供应链门禁窗口内
1020
+ // 暂缓提示:确有新版本,但仍在 pnpm 24h 发布冷却期内
1021
1021
  hubPendingDesc: 'The latest version has been released, but it is still within the 24-hour window where pnpm silently falls back to the older version, so the update is not offered yet. The update entry will appear here shortly.',
1022
1022
  // 头部「关注我们」弹窗:平台介绍(内容由 Worker /about 以 Markdown 推送)
1023
1023
  followUs: 'Follow us',
@@ -33,7 +33,7 @@ declare const __PLUGIN_VERSION__: string
33
33
  /**
34
34
  * 插件当前版本号,供头部标题展示「DSH Plugin Hub v0.1.1」。
35
35
  * tsdown 构建时用 define 把 __PLUGIN_VERSION__ 替换成 package.json 的版本号;
36
- * node --test 直接 import 本模块时该标识符不存在,typeof 守卫兜底为空串。
36
+ * node --test 直接 import 本模块时该标识符不存在,typeof 守卫缺省为空串。
37
37
  */
38
38
  export const PLUGIN_VERSION: string =
39
39
  typeof __PLUGIN_VERSION__ === 'string' ? __PLUGIN_VERSION__ : ''
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 自愈缺失的插件 CSS 标签。
2
+ * 自动补回缺失的插件 CSS 标签。
3
3
  *
4
4
  * CSS 注入是 bundle 工厂的一次性副作用(以「标签已存在」为守卫)。宿主重启后页面仍在,
5
5
  * 但宿主 HMR 链可能移除 <style data-plugin> 标签,而内存模块缓存仍持有旧工厂,工厂不再
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * tsdown 的 `dsh-css-modules-inline` 构建插件会在工厂执行时把每个模块的 { tagId, css }
9
9
  * 登记进全局列表 `__DSH_PLUGIN_CSS__`。此工具随后比对列表与实时 DOM,补注入缺失的标签,
10
- * 把「点一圈才自愈」变成挂载 / 切换视图时自动完成。
10
+ * 把「点一圈才恢复」变成挂载 / 切换视图时自动完成。
11
11
  */
12
12
 
13
13
  interface PluginCssEntry {
@@ -16,7 +16,7 @@ export interface NotificationRecord {
16
16
  /** owner/repo(可能为空:请求层失败但拿不到仓库时) */
17
17
  repo: string
18
18
  /** 实际动作(install/update/uninstall):成功/失败通知据此区分「安装成功」与「更新成功」。
19
- * 缺省按 kind 兜底(历史记录可能没有该字段)。更新提醒(kind='update')不用此字段。 */
19
+ * 缺省按 kind 归类(历史记录可能没有该字段)。更新提醒(kind='update')不用此字段。 */
20
20
  action?: 'install' | 'update' | 'uninstall'
21
21
  /** 失败时的完整错误日志;成功记录为空串 */
22
22
  message: string
@@ -150,7 +150,7 @@ export function removeNotification(id: number): NotificationRecord[] {
150
150
 
151
151
  export type FailureKind = 'npmTooOld' | 'nodeEngine' | 'dshMissing' | 'gitMissing' | 'pnpmMissing' | 'npmMissing' | 'pnpmStore' | 'pnpmModulesMismatch' | 'profileYamlCorrupted' | 'profileLocalBuild' | 'profileState' | 'pnpmWorkspace' | 'originRejected' | 'pnpmPolicy' | 'pnpmUnusedPatch' | 'pnpmMissingDep' | 'pluginMissingDep' | 'registryVersionStale' | 'registryAuth' | 'profileExclusive' | 'pnpmLinkedDirMissing' | 'fileLocked' | 'accessDenied' | 'fsUnavailable' | 'buildToolchainMissing' | 'pnpmIgnoredBuild' | 'pluginPrepare' | 'hostIncompatible' | 'tlsTrust' | 'network' | 'hostCliSpawn' | 'hubState' | 'profileBundleMissing' | 'timeout' | 'profileLock' | 'dshTooOld' | 'localEnv' | 'hub'
152
152
 
153
- /** 失败动作口径:标题、上报正文与提示文案按安装/更新/卸载取词,不写死「安装」。 */
153
+ /** 失败动作规则:标题、上报正文与提示文案按安装/更新/卸载取词,不写死「安装」。 */
154
154
  export type FailureAction = 'install' | 'update' | 'uninstall'
155
155
 
156
156
  /** 动作词(英文):名词短语,标题与句子里都成立。 */
@@ -163,7 +163,7 @@ export function actionWordZh(action: FailureAction): string {
163
163
  return action === 'uninstall' ? '卸载' : action === 'update' ? '更新' : '安装'
164
164
  }
165
165
 
166
- /** 记录的动作:老记录只有 kind,按 kind 兜底。 */
166
+ /** 记录的动作:老记录只有 kind,按 kind 归类。 */
167
167
  export function actionOfRecord(rec: { kind: NotificationRecord['kind']; action?: FailureAction }): FailureAction {
168
168
  if (rec.action !== undefined) return rec.action
169
169
  return rec.kind === 'uninstall' ? 'uninstall' : rec.kind === 'update' ? 'update' : 'install'
@@ -231,7 +231,7 @@ export function pnpmPolicyHintParams(message: string): Record<string, string> {
231
231
  * (只影响这个插件 → 才引导提 Issue)。各分支判定顺序有依赖,见函数内注释。
232
232
  */
233
233
  export function classifyFailure(message: string): FailureKind {
234
- // 宿主版本门禁:插件声明的兼容范围不含当前 dsh 版本,宿主在安装前直接拒绝。
234
+ // 宿主版本要求:插件声明的兼容范围不含当前 dsh 版本,宿主在安装前直接拒绝。
235
235
  // 必须最先判 —— 这是宿主自己给的结论,不是 pnpm 的安装失败
236
236
  if (/installation rejected:.*incompatible with dsh/i.test(message)) return 'hostIncompatible'
237
237
  // desktop profile 由桌面应用独占:外部 dsh CLI 无权管理,宿主在安装前直接拒绝。
@@ -266,7 +266,7 @@ export function classifyFailure(message: string): FailureKind {
266
266
  // pnpm 供应链策略拦截(缺 tarball integrity / 校验和不匹配 / minimumReleaseAge / untrusted origin)。
267
267
  // 必须在 pnpmIgnoredBuild 之前
268
268
  if (missingTarballIntegrity(message) || mismatchedTarballIntegrity(message) || /ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION|ERR_PNPM_NO_MATURE_MATCHING_VERSION|Minimum release age|minimumReleaseAge|untrusted origin/i.test(message)) return 'pnpmPolicy'
269
- // profile 残留旧版本 patch 声明,pnpm 中止安装。必须在兜底之前
269
+ // profile 残留旧版本 patch 声明,pnpm 中止安装。必须排在默认归类之前
270
270
  if (/ERR_PNPM_UNUSED_PATCH|patches were not used/i.test(message)) return 'pnpmUnusedPatch'
271
271
  // profile 里的 YAML 配置解析失败(pnpm-workspace.yaml / pnpm-lock.yaml 有重复键、缩进坏了):
272
272
  // pnpm 在依赖解析之前就退出,装任何插件都会同样失败,属本机 profile 配置问题。
@@ -334,7 +334,7 @@ export function classifyFailure(message: string): FailureKind {
334
334
  if (/git-hosted plugins build on install via their prepare script|add the exact key pnpm printed above under allowBuilds/i.test(message)) return 'pnpmIgnoredBuild'
335
335
  // 宿主启动插件管理时找不到某个 profile bundle(`cannot resolve profile bundle "<pkg>"`):
336
336
  // 该 profile 声明的依赖没装全(node_modules 被删过 / 上次安装中断 / 桌面端更新不完整),
337
- // 与本次要装的插件无关 —— 必须在 hub 兜底之前,否则又会被当成未知错误丢给我们
337
+ // 与本次要装的插件无关 —— 必须排在 hub 的默认归类之前,否则又会被当成未知错误丢给我们
338
338
  if (/cannot resolve profile bundle/i.test(message)) return 'profileBundleMissing'
339
339
  // 宿主自身入口起不来(服务端 [host-spawn] 标记):环境问题,给提示、不引导提 Issue。
340
340
  // 不匹配 `spawn … ENOENT` 文本 —— 那个形态任何 spawn(curl/git/taskkill…)都会产出
@@ -354,7 +354,7 @@ export function classifyFailure(message: string): FailureKind {
354
354
  return 'hub'
355
355
  }
356
356
 
357
- /** 「特征指向本机环境但说不清具体哪一类」的兜底判据:系统调用错误码(权限/路径过长/设备异常)、
357
+ /** 「特征指向本机环境但说不清具体哪一类」的最后的判断规则:系统调用错误码(权限/路径过长/设备异常)、
358
358
  * 证书与代理校验失败、本地配置文件(~/.npmrc、profile 的 package.json)损坏。
359
359
  * 只做排除用:命中它表示「不是插件侧问题」,不代表我们已经定位到原因。 */
360
360
  const LOCAL_ENV_RE = /os error \d{1,3}\b|ERROR_[A-Z_]{4,}|\bE(?:ACCES|PERM|NOENT|NAMETOOLONG|INVAL|PROTO|ISDIR|NOTDIR|EXIST|BADF|NOTEMPTY|JSONPARSE|NOMEM)\b|JavaScript heap out of memory|Unexpected token .{0,60}in JSON|SELF_SIGNED_CERT_IN_CHAIN|UNABLE_TO_VERIFY_LEAF_SIGNATURE|ERR_TLS_CERT_ALTNAME_INVALID|unable to verify the first certificate|self[- ]signed certificate|ERR_INVALID_URL|invalid registry|proxy authentication|407 Proxy/i
@@ -386,7 +386,7 @@ export const MAX_CORE_CHARS = 5000
386
386
  const MAX_LINE_CHARS = 400
387
387
 
388
388
  /**
389
- * 展示用格式化:宿主门禁消息里的 peerDependencies 是压缩成一行的 JSON,
389
+ * 展示用格式化:宿主兼容性提示里的 peerDependencies 是压缩成一行的 JSON,
390
390
  * 直接展示像一团乱码;展开成缩进格式,解析失败则原样返回。
391
391
  */
392
392
  export function formatFailureMessage(message: string): string {
@@ -420,7 +420,7 @@ export function summarizeError(message: string, maxChars: number = MAX_CORE_CHAR
420
420
  }
421
421
  let out: string
422
422
  if (core.length === 0) {
423
- // 整段本来就是短消息时直接给全量:head+tail 兜底会把同一段重复两遍,正文反而更难读
423
+ // 整段本来就是短消息时直接给全量:head+tail 的截断方式会把同一段重复两遍,正文反而更难读
424
424
  const whole = message.trim()
425
425
  if (whole.length <= maxChars) return whole
426
426
  // 无关键行时退化为「头部 + 尾部」快照:优先保尾部完整,头部按预算压缩,总长不超上限
@@ -30,8 +30,7 @@ export function installTargetOf(p: HubPlugin): string {
30
30
  /** 展示用安装命令(复制/弹窗):目录下发的权威命令优先(CLI-only 插件如 dsh-tui
31
31
  * 需专属 profile(--profile dsh-tui),无法从 npm 包名推断,必须用官方命令);
32
32
  * 常规插件无目录命令时用 npm 包名回退生成。
33
- * version 为已过供应链门禁的版本:带上它,手抄命令与原位安装的执行结果一致,
34
- * 不会因 pnpm 门禁静默回退到很旧的版本。 */
33
+ * version 为已过发布冷却期的版本:带上它,手抄命令与一键安装的执行结果一致。 */
35
34
  export function installCommandOf(p: HubPlugin, withProfile = false, version?: string): string {
36
35
  const cmd = p.install?.command
37
36
  if (cmd) return cmd
@@ -42,7 +42,7 @@ export interface InstalledItem {
42
42
  catalogVersion: string | null
43
43
  /** 是否有可用更新(有版本比版本,无版本比仓库更新时间) */
44
44
  hasUpdate: boolean
45
- /** 更新被供应链门禁暂缓时的可重试时刻(毫秒);无更新/非门禁原因 → null */
45
+ /** 更新暂缓(新版本还没过冷却期)的可重试时刻(毫秒);无更新/非冷却期原因 → null */
46
46
  pendingUntil: number | null
47
47
  /** 是否已加载进运行中 loader(官方 ctx.loader 对账);false = 已安装但未挂载(装完未重启) */
48
48
  loaded: boolean
@@ -3,15 +3,15 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * 供应链门禁的时间判定(纯函数):pnpm 会静默回退发布未满门槛的版本,
7
- * 故发布太新的版本不该提示更新。Hub 自身更新与插件更新共用同一门槛。
6
+ * 发布冷却期的时间判定(纯函数):pnpm 会不提示地回退发布还不满 24 小时的版本,
7
+ * 故发布太新的版本不该提示更新。Hub 自身更新与插件更新共用同一规则。
8
8
  */
9
9
  import type { HubUpdateInfo, HubUpdateState, HubUpdateVersion } from '../types.ts'
10
10
 
11
- /** pnpm 供应链门禁:dsh profile 默认 minimumReleaseAge=1440(分钟);+30 分钟缓冲抵消网络/CDN 边缘时间偏差。 */
11
+ /** pnpm 发布冷却期:dsh profile 默认 minimumReleaseAge=1440(分钟);+30 分钟缓冲抵消网络/CDN 边缘时间偏差。 */
12
12
  export const MIN_RELEASE_AGE_MS = 24 * 60 * 60 * 1000 + 30 * 60 * 1000
13
13
 
14
- /** 发布时间是否仍在门禁窗口内(太新 → 暂缓提示);无值/解析不出返回 false(不拦真实更新)。 */
14
+ /** 发布时间是否仍在冷却期内(太新 → 暂缓提示);无值/解析不出返回 false(不拦真实更新)。 */
15
15
  export function isReleaseTooFresh(publishedAt: string | null | undefined, now: number): boolean {
16
16
  if (typeof publishedAt !== 'string' || publishedAt === '') return false
17
17
  const ms = Date.parse(publishedAt)
@@ -61,20 +61,20 @@ export function compareVersions(a: string | null | undefined, b: string | null |
61
61
  return 0
62
62
  }
63
63
 
64
- /** 「可更新」判定结果:是否提示,以及被供应链门禁拦下时的到期时刻。 */
64
+ /** 「可更新」判定结果:是否提示,以及还没过冷却期时的可重试时刻。 */
65
65
  export interface UpdateDecision {
66
66
  offered: boolean
67
- /** 发布未满门槛 → 可重试时刻(毫秒);非门禁原因(npm 上没有更高版本)→ null */
67
+ /** 发布还不满冷却期 → 可重试时刻(毫秒);非冷却期原因(npm 上没有更高版本)→ null */
68
68
  pendingUntil: number | null
69
69
  }
70
70
 
71
71
  /**
72
- * 是否值得提示「可更新」:npm 上最新可安装版本确实比已装版本新,且已过供应链门禁。
72
+ * 是否值得提示「可更新」:npm 上最新可安装版本确实比已装版本新,且已过发布冷却期。
73
73
  *
74
74
  * 更新命令执行的是 `pkg@latest`,所以「目录里标的版本」不是最终能装上的版本:
75
75
  * 作者可能只打了 GitHub release 没发 npm —— 此时提示会变成永远点不掉的循环。
76
76
  * 任一信息缺失(查不到发布信息 / 没有已装版本记录)返回 offered,交回调用方原有判定:
77
- * 宁可提示,也不因为一次查询失败把真实更新吃掉。
77
+ * 宁可提示,也不因为一次查询失败漏掉真实更新。
78
78
  */
79
79
  export function updateDecision(
80
80
  release: { version: string; publishedAt: string } | null,
@@ -104,8 +104,8 @@ export function isUpdateOffered(
104
104
  /**
105
105
  * 选出本次真正该提示的更新版本。
106
106
  *
107
- * 只看最新版会漏更新:连续发版时间隔小于供应链门禁时,最新版永远「太新」,
108
- * 提示被整体跳过。这里把最新版与 recentVersions 一起当候选,取「高于当前版本且已过门禁」
107
+ * 只看最新版会漏更新:连续发版时间隔小于发布冷却期时,最新版永远「太新」,
108
+ * 提示被整体跳过。这里把最新版与 recentVersions 一起当候选,取「高于当前版本且已过冷却期」
109
109
  * 的最高版本;一个都没有但仍存在更高版本 → pending(暂缓,不谎称已是最新)。
110
110
  */
111
111
  export function pickHubUpdate(info: HubUpdateInfo, currentVersion: string, now: number): { state: HubUpdateState; target: HubUpdateVersion | null } {
@@ -35,9 +35,11 @@ export function pluginDetailUrl(plugin: HubPlugin, langPath: string): string {
35
35
  /**
36
36
  * 失败弹窗里仓库地址的跳转目标:官网详情页(含插件收录信息),
37
37
  * 与 issue 正文的 Catalog 链接同一形式(单级 /plugins/{repo})。
38
+ * 官网静态导出只生成全小写路径,GitHub 仓库名大小写不敏感,这里统一转小写,
39
+ * 否则 HuaGuoXingKong/dsh-prompt-rewriter 这类仓库的链接会落到 404。
38
40
  */
39
41
  export function pluginSiteUrl(repo: string): string {
40
- return siteUrl(`plugins/${repo}`)
42
+ return siteUrl(`plugins/${repo.toLowerCase()}`)
41
43
  }
42
44
 
43
45
  /** 错误原因标题(对外英文):点明问题出在哪一侧,便于 issue 列表分流;
@@ -228,7 +230,7 @@ export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | nu
228
230
  * 错误摘要,供插件中心维护者分诊 —— 不再把环境问题/插件中心自身问题误投给插件作者。
229
231
  */
230
232
  export type HubIssueVariant =
231
- /** 缺省:安装器主动把失败送到插件中心分诊(未分类兜底) */
233
+ /** 缺省:安装器主动把失败送到插件中心分诊(未分类时的默认去向) */
232
234
  | 'triage'
233
235
  /** 从判定由插件中心自己产出的分支(插件打包问题 / allowBuilds 拦截)的次要入口提交:用户怀疑是误报 */
234
236
  | 'misjudge'
@@ -249,7 +251,7 @@ export function hubIssueUrl(
249
251
  const zhWord = actionWordZh(action)
250
252
  const repo = pluginRepo && pluginRepo.trim() !== '' ? pluginRepo.trim() : 'unknown'
251
253
  const title = variant === 'misjudge'
252
- // 标题就点明「可能是误判」,维护者一眼能区分它和未分类兜底
254
+ // 标题就点明「可能是误判」,维护者一眼能区分它和未分类时的默认去向
253
255
  ? `[dsh-plugin-hub] possible misjudgment: ${repo}`
254
256
  : `[dsh-plugin-hub] ${reasonTitleOf(kind, action)}: ${repo}`
255
257
  const code = coreErrorCode(message)
@@ -264,7 +266,7 @@ export function hubIssueUrl(
264
266
  ...(variant === 'misjudge'
265
267
  // 这些分支的判据由插件中心自己产出、或掺了我们的动作(自动放行 allowBuilds)——
266
268
  // 一旦我们自己误报,用户就被送去作者仓库、我们永远收不到反馈。
267
- // 正文写清它来自哪条分支、判据是谁给的,维护者才不会把它当成未分类兜底。
269
+ // 正文写清它来自哪条分支、判据是谁给的,维护者才不会把它当成未分类时的默认去向。
268
270
  ? [
269
271
  '- Filed from: a branch that normally routes to the plugin repo — filed here because that verdict may be our own misjudgment.',
270
272
  '- Source of that verdict: produced by dsh-plugin-hub\'s own check or its auto-allow retry, not by plain pnpm output.',
@@ -216,7 +216,7 @@
216
216
  line-height: 18px;
217
217
  color: var(--hub-text-tertiary);
218
218
  margin: 0;
219
- /* 品牌副标题强制单行:中文/英文都不换行,超宽时省略号截断兜底 */
219
+ /* 品牌副标题强制单行:中文/英文都不换行,超宽时用省略号截断 */
220
220
  white-space: nowrap;
221
221
  overflow: hidden;
222
222
  text-overflow: ellipsis;
@@ -876,7 +876,7 @@
876
876
 
877
877
  /* —— 安装失败记录 —— */
878
878
 
879
- /* 失败记录列表:纵向排列,高度随记录数自适应 —— 记录少时贴合内容,记录多时由窗体 max-height 兜底滚动 */
879
+ /* 失败记录列表:纵向排列,高度随记录数自适应 —— 记录少时贴合内容,记录多时由窗体 max-height 限制滚动 */
880
880
  .failList {
881
881
  display: flex;
882
882
  flex-direction: column;
@@ -904,7 +904,7 @@
904
904
 
905
905
  /* —— 任务通知中心(成功 + 失败) —— */
906
906
 
907
- /* 通知列表:纵向排列,高度随记录数自适应 —— 记录少时贴合内容,记录多时由窗体 max-height 兜底滚动 */
907
+ /* 通知列表:纵向排列,高度随记录数自适应 —— 记录少时贴合内容,记录多时由窗体 max-height 限制滚动 */
908
908
  .noticeList {
909
909
  display: flex;
910
910
  flex-direction: column;
@@ -105,7 +105,7 @@
105
105
 
106
106
  /* —— 通知中心入口 —— */
107
107
 
108
- /* 通知铃铛按钮:位于「设置」tab 后边、靠右对齐(margin-left:auto 吃掉 tab 剩余空间),
108
+ /* 通知铃铛按钮:位于「设置」tab 后边、靠右对齐(margin-left:auto 占满 tab 剩余空间),
109
109
  在 38px tab 栏里垂直居中;常态透明底只显铃铛轮廓,hover 浅灰,不抢 tab 视觉层级 */
110
110
  .noticeBtn {
111
111
  position: relative;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * 插件中心设计变量。
6
6
  *
7
- * 对宿主 --dsw-alias-* 主题变量的语义别名,各带原始浅色字面量,供未定义底层变量的宿主兜底。
7
+ * 对宿主 --dsw-alias-* 主题变量的语义别名,各带原始浅色字面量,供未定义底层变量的宿主使用。
8
8
  * 声明在 :root 与暗色 body 上,让每种主题都按自己的变量集重新解析别名,再由所有板块元素继承。
9
9
  *
10
10
  * 各区域样式表不要写颜色字面量:在此引用 --hub-*,新样式即可自动跟随主题。 */
@@ -110,8 +110,8 @@ export interface HubUpdateInfo {
110
110
  notes?: string | Record<string, string> | null
111
111
  /**
112
112
  * 最近若干发版(含最新版),按版本号降序无关、由客户端自行排序。
113
- * 用途:连续快速发版时,最新版往往还在 pnpm 的 24h 供应链门禁内;
114
- * 客户端据此回退到「更早、但已过门禁」的版本提示更新,而不是整体跳过。
113
+ * 用途:连续快速发版时,最新版往往还在 pnpm 的 24h 发布冷却期内;
114
+ * 客户端据此回退到「更早、但已过冷却期」的版本提示更新,而不是整体跳过。
115
115
  */
116
116
  recentVersions?: HubUpdateVersion[]
117
117
  }
@@ -126,8 +126,8 @@ export interface HubUpdateVersion {
126
126
  /**
127
127
  * Hub 自我更新状态 —— 供版本弹窗选文案与按钮,四态互斥:
128
128
  * - `none` 未取到接口中心信息(拉取失败)→ 只说当前版本,不提示更新
129
- * - `available` 远端有更新且已过供应链安全门槛 → 提示更新
130
- * - `pending` 远端有更新但发布未满门槛(见 useCatalog)→ 暂缓提示并说明原因
129
+ * - `available` 远端有更新且已过发布冷却期 → 提示更新
130
+ * - `pending` 远端有更新但发布还不满冷却期(见 useCatalog)→ 暂缓提示并说明原因
131
131
  * - `latest` 与远端最新版一致 → 确已最新
132
132
  * 「有新版但暂缓提示」必须与「确已最新」分开:两者若共用文案,弹窗会在本机版本落后时
133
133
  * 谎称「你正在使用 v{远端最新版},已是最新」。
@@ -42,7 +42,7 @@ function fetchCatalogOnce(target: string, proxy: string): ReturnType<typeof fetc
42
42
  }
43
43
 
44
44
  /** 读缓存:文件存在、JSON 合法、未超过 maxAgeMs → 返回原始响应字符串;否则 null(视为 miss)。
45
- * maxAgeMs 传 Infinity 即「任意年龄的缓存」,供实时拉取失败时兜底使用。 */
45
+ * maxAgeMs 传 Infinity 即「任意年龄的缓存」,供实时拉取失败时使用。 */
46
46
  function readCatalogCache(file: string, maxAgeMs: number): string | null {
47
47
  try {
48
48
  const entry = JSON.parse(readFileSync(file, 'utf8')) as CatalogCacheEntry
@@ -56,7 +56,7 @@ function readCatalogCache(file: string, maxAgeMs: number): string | null {
56
56
  return null
57
57
  }
58
58
 
59
- /** 写缓存:目录不存在则创建;写失败静默忽略(缓存只是加速,不影响功能)。 */
59
+ /** 写缓存:目录不存在则创建;写失败直接忽略(缓存只是加速,不影响功能)。 */
60
60
  function writeCatalogCache(file: string, body: string): void {
61
61
  try {
62
62
  mkdirSync(dirname(file), { recursive: true })
@@ -108,7 +108,7 @@ export function registerCatalogRoutes(webServer: WebServerService, profile: stri
108
108
  handler: async (request, response) => {
109
109
  if (!requireMethod(request, response, 'GET')) return
110
110
  // 目录/统计数据服务端代理:浏览器不直连 dsh-plugin.org,统一经此路由
111
- // 转发(curl 子进程注入代理 env),与 npm / git 安装通道走同一代理口径,
111
+ // 转发(curl 子进程注入代理 env),与 npm / git 安装通道走同一代理规则,
112
112
  // 「npm / git / 目录数据请求统一走该代理」的设置文案因此真实生效。
113
113
  const url = new URL(request.url ?? '/', 'http://localhost')
114
114
  const settings = loadSettings(profile)
@@ -145,7 +145,7 @@ export function registerCatalogRoutes(webServer: WebServerService, profile: stri
145
145
  sendJson(response, 200, JSON.parse(stale))
146
146
  return
147
147
  } catch {
148
- // 兜底缓存也损坏:落到下面的 502
148
+ // 备用缓存也损坏:落到下面的 502
149
149
  }
150
150
  }
151
151
  // 带上 curl 退出码与 stderr 末行:只回 error 字段时,用户贴出来的响应里
@@ -14,7 +14,7 @@ import { gitLsRemote, probeUrl, systemProxy } from '../services/probe.ts'
14
14
  import { appendLog } from '../services/log.ts'
15
15
  import { readJsonBody, requireTrustedPost, sendJson, type WebRoute, type WebServerService } from './shared.ts'
16
16
 
17
- /** 本机实际生效的 npm registry:诊断与安装同口径 —— 设置里留空时读用户 npm 配置
17
+ /** 本机实际生效的 npm registry:诊断与安装同一套规则 —— 设置里留空时读用户 npm 配置
18
18
  * (含 profile 目录的 .npmrc),而不是硬探官方源,避免「能装但诊断说源不通」。
19
19
  * 读不到(npm 缺失/超时/输出异常)时回退官方源。 */
20
20
  function configuredNpmRegistry(profile: string): string {
@@ -75,7 +75,7 @@ export function registerDiagnosticsRoutes(webServer: WebServerService, profile:
75
75
  }
76
76
  const settings = loadSettings(profile)
77
77
  // npm 通道:设置里配置了镜像源就探测该镜像(安装通道吃同一个 registry);
78
- // 未配置(空串)时探测本机 npm 配置里实际生效的源 —— 与安装同口径。
78
+ // 未配置(空串)时探测本机 npm 配置里实际生效的源 —— 与安装同一套规则。
79
79
  const registry = settings.npmRegistry.replace(/\/+$/, '') || configuredNpmRegistry(profile)
80
80
  // 连通性自检打各通道的轻量资源:npm 源拉包元数据、GitHub 行探小仓库、
81
81
  // 目录站按 Range 取 1KB(探市场真实下载的 plugins.zh.json,但不整份下载)。
@@ -91,7 +91,7 @@ export function registerDiagnosticsRoutes(webServer: WebServerService, profile:
91
91
  { key: 'catalog', url: 'https://api.dsh-plugin.org/plugins.zh.json', display: '', cmd: 'curl -r 0-1023 https://api.dsh-plugin.org/plugins.zh.json', range: '0-1023' },
92
92
  ]
93
93
  // 配置了 HTTP 代理:追加一行代理诊断 —— 用该代理打 github.com(安装通道真实访问的地址),
94
- // 验证「代理能不能把请求带出去」。与安装同口径:curl 子进程注入该代理 env。
94
+ // 验证「代理能不能把请求带出去」。与安装同一套规则:curl 子进程注入该代理 env。
95
95
  // 设置里未配置代理时不显示该行(跟随系统代理/直连,无「代理本身」可验证)。
96
96
  if (settings.proxy !== '') {
97
97
  allChecks.push({ key: 'proxy', url: 'https://github.com', display: settings.proxy, cmd: `curl -s -o /dev/null -x ${settings.proxy} https://github.com`, proxy: settings.proxy })
@@ -114,7 +114,7 @@ export function registerDiagnosticsRoutes(webServer: WebServerService, profile:
114
114
  const results: Array<{ key: string; ok: boolean }> = []
115
115
  for (const c of checks) {
116
116
  send({ type: 'probe', key: c.key, cmd: c.cmd, display: c.display })
117
- // git 行走真实克隆握手(gitLsRemote),其余走 curl 探测,两路都带超时兜底
117
+ // git 行走真实克隆握手(gitLsRemote),其余走 curl 探测,两路都带超时保护
118
118
  const r = c.git
119
119
  ? await gitLsRemote(c.url, c.proxy ?? effectiveProxy, 6000)
120
120
  : await probe(c.url, c.proxy, c.range)
@@ -17,7 +17,7 @@ import { profileDirectory } from '../services/profile/profile.ts'
17
17
  import { readInstalled } from './catalog.ts'
18
18
  import { COMMAND_TIMEOUT_MS, readJsonBody, requireTrustedPost, sendJson, type WebRoute, type WebServerService } from './shared.ts'
19
19
 
20
- /** 安装/卸载 CLI 的子进程环境:透传宿主环境并注入 HTTP(S) 代理,
20
+ /** 安装/卸载 CLI 的子进程环境:原样传递宿主环境并注入 HTTP(S) 代理,
21
21
  * 代理来源与诊断一致(设置代理 → 系统代理 → 宿主 env);registry 由任务队列按候选源逐次注入。 */
22
22
  function mutationEnv(settings: HubSettings): NodeJS.ProcessEnv {
23
23
  const env: NodeJS.ProcessEnv = { ...process.env, CI: 'true' }
@@ -67,11 +67,11 @@ export function registerInstallRoutes(webServer: WebServerService, profile: stri
67
67
  const lang = typeof body === 'object' && body !== null && (body as { lang?: unknown }).lang === 'en'
68
68
  ? 'en'
69
69
  : 'zh'
70
- // 代理口径与诊断/安装通道一致:设置里的代理 → 系统代理 → 环境变量 → 直连。
70
+ // 代理规则与诊断/安装通道一致:设置里的代理 → 系统代理 → 环境变量 → 直连。
71
71
  const effectiveProxy = settings.proxy !== ''
72
72
  ? settings.proxy
73
73
  : (systemProxy() ?? process.env.HTTPS_PROXY ?? process.env.https_proxy ?? '')
74
- // 安装用的 npm 源:设置里显式选择优先;未配置时读本机 npm 配置(与诊断同口径)。
74
+ // 安装用的 npm 源:设置里显式选择优先;未配置时读本机 npm 配置(与诊断同一套规则)。
75
75
  // 只有公共源允许失败后换源;自定义/内网源保持单一候选,避免私有包名泄露给公共源。
76
76
  const registries = registryCandidates(
77
77
  settings.npmRegistry,
@@ -79,7 +79,7 @@ export function registerInstallRoutes(webServer: WebServerService, profile: stri
79
79
  settings.registryFallback,
80
80
  )
81
81
  // 全局 npm 安装(`npm install -g <pkgs>`):不进任何 profile、无需宿主重启。
82
- // 包列表优先取 body.globalNpm,兜底从 rawRepo 解析,保证直接调 API 传完整命令也能识别。
82
+ // 包列表优先取 body.globalNpm,取不到时再从 rawRepo 解析,保证直接调 API 传完整命令也能识别。
83
83
  const bodyGlobalNpm = Array.isArray((body as { globalNpm?: unknown }).globalNpm)
84
84
  ? (body as { globalNpm: unknown[] }).globalNpm.filter((x): x is string => typeof x === 'string' && x !== '')
85
85
  : []
@@ -90,7 +90,7 @@ export function registerInstallRoutes(webServer: WebServerService, profile: stri
90
90
  sendJson(response, 400, { error: 'unsupported install target' })
91
91
  return
92
92
  }
93
- // 通道门禁与安装通道同构:全局安装本质是 npm 操作,吃「启用 NPM 安装」开关
93
+ // 通道校验与安装通道同构:全局安装本质是 npm 操作,吃「启用 NPM 安装」开关
94
94
  if (source !== 'catalog' && !settings.enableNpmInstall) {
95
95
  sendJson(response, 403, { error: 'npm installs are disabled by the security settings' })
96
96
  return
@@ -123,8 +123,8 @@ export function registerInstallRoutes(webServer: WebServerService, profile: stri
123
123
  return
124
124
  }
125
125
  const target = rawRepo
126
- // 钉住精确版本(Hub 自我更新):最新版可能仍在 pnpm 供应链门禁内,
127
- // 客户端提示的是「已过门禁」的版本,服务端必须按该版本安装而不是 @latest。
126
+ // 锁定精确版本(Hub 自我更新):最新版可能仍在 pnpm 发布冷却期内,
127
+ // 客户端提示的是「已过冷却期」的版本,服务端必须按该版本安装而不是 @latest。
128
128
  const pinVersion = typeof (body as { version?: unknown }).version === 'string'
129
129
  ? (body as { version: string }).version.trim()
130
130
  : ''
@@ -182,12 +182,12 @@ export function registerInstallRoutes(webServer: WebServerService, profile: stri
182
182
  }
183
183
  ordered = [chosen, ...registries.filter((r) => r !== chosen)]
184
184
  }
185
- // 新安装钉住「已过供应链门禁」的版本:pnpm 对发布未满 24h 的版本会静默回退到很旧的版本,
186
- // 用户装完才发现功能缺失且没有任何提示。更新已有目标时客户端已按门禁筛选,继续走 @latest。
185
+ // 新安装锁定「已过发布冷却期」的最高稳定版:pnpm 对发布还不满 24 小时的版本会不提示地回退,
186
+ // 锁定后装到的就是预期版本。更新已有目标时客户端已按冷却期筛选,继续走 @latest。
187
187
  let installSpec = pinVersion !== '' ? `${target}@${pinVersion}` : undefined
188
188
  if (installSpec === undefined && already === null) {
189
- // 解析源与安装首选源一致,钉住的版本一定存在于真正会用的那个源;解析失败则不钉,不阻断安装。
190
- // 超时压到 5s:网络刚探通,慢也只在极端情况,不能为查版本把安装请求拖住 8s
189
+ // 解析源与安装首选源一致,锁定的版本一定存在于真正会用的那个源;解析失败则不锁定,不阻断安装。
190
+ // 超时 5s:网络刚探测通过,卡住时也不能把安装请求拖到默认的 8s
191
191
  const release = await lookupReleaseAge(target, ordered[0] ?? '', effectiveProxy, 5000)
192
192
  if (release !== null && release.installable !== null) installSpec = `${target}@${release.installable.version}`
193
193
  }
@@ -200,7 +200,7 @@ export function registerInstallRoutes(webServer: WebServerService, profile: stri
200
200
  // 后台启动 CLI;客户端轮询 /status 获取进度
201
201
  const task = startPluginMutation({
202
202
  // mode=update(已安装目标的覆盖更新)以 update 动作入队:
203
- // 日志按 update 类别落盘(update.start/done/fail),实际 CLI 执行仍是 add <target>@latest
203
+ // 日志按 update 类别写入磁盘(update.start/done/fail),实际 CLI 执行仍是 add <target>@latest
204
204
  action: mode === 'update' ? 'update' : 'add',
205
205
  profile,
206
206
  target,
@@ -208,7 +208,7 @@ export function registerInstallRoutes(webServer: WebServerService, profile: stri
208
208
  displayTarget: githubRepoOf(displayRepo) ?? undefined,
209
209
  // 覆盖更新:npm 命令显式 @latest(pnpm add 无版本对已存在依赖是幂等的,不加版本不会真更新)
210
210
  updateNpm: mode === 'update',
211
- // 运行中 loader:安装成功后把新插件热挂进 loader,非桌面端 + 纯服务端插件无需重启即生效
211
+ // 运行中 loader:安装成功后把新插件热加载进 loader,非桌面端 + 纯服务端插件无需重启即生效
212
212
  loader,
213
213
  timeoutMs: COMMAND_TIMEOUT_MS,
214
214
  env: mutationEnv(settings),
@@ -4,7 +4,7 @@
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
6
  * npm 包发布时间查询路由:按已安装插件的 npm 包名按需查询最新版本的发布时间,
7
- * 供客户端判定 pnpm 24h 供应链门禁(发布未满门槛的版本不提示更新)。
7
+ * 供客户端判定 pnpm 24h 发布冷却期(发布还不满 24 小时的版本不提示更新)。
8
8
  */
9
9
  import { systemProxy } from '../services/probe.ts'
10
10
  import { lookupReleaseAge, parseReleaseAgeRequest } from '../services/npm-release-age.ts'
@@ -27,12 +27,12 @@ export function registerNpmReleaseAgeRoutes(webServer: WebServerService, profile
27
27
  return
28
28
  }
29
29
  const settings = loadSettings(profile)
30
- // 代理口径与安装/目录一致:设置里的代理 → 系统代理 → 环境变量 → 直连
30
+ // 代理规则与安装/目录一致:设置里的代理 → 系统代理 → 环境变量 → 直连
31
31
  const proxy = settings.proxy !== ''
32
32
  ? settings.proxy
33
33
  : (systemProxy() ?? process.env.HTTPS_PROXY ?? process.env.https_proxy ?? '')
34
34
  // 并发查询:单包各自带超时且失败即 null,整体不因任一包失败而失败
35
- // 查询源与安装首选源同口径(设置 → 本机 npm 配置):安装钉住的版本必须来自真正要用的那个源
35
+ // 查询源与安装首选源同一套规则(设置 → 本机 npm 配置):安装锁定的版本必须来自真正要用的那个源
36
36
  const registry = settings.npmRegistry !== ''
37
37
  ? settings.npmRegistry
38
38
  : userNpmRegistry(profileDirectory(profile))
@@ -42,7 +42,7 @@ function findPids() {
42
42
 
43
43
  const targets = findPids()
44
44
  // 一个监听进程都没找到 = 端口探测本身就失败了(netstat 输出格式 / 本地化差异),
45
- // 旧宿主仍占着端口。此时再拉一个 dsh web 只会在日志里以 EADDRINUSE 静默退出,
45
+ // 旧宿主仍占着端口。此时再拉一个 dsh web 只会在日志里以 EADDRINUSE 无提示地退出,
46
46
  // 用户却看到「重启完成」——不制造这种假象,留一行可诊断的记录后退出。
47
47
  if (targets.length === 0) {
48
48
  logLine('restart aborted: no listener found on port ' + port)