dsh-plugin 2.3.2 → 2.3.3
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/README.en.md +3 -9
- package/README.md +3 -9
- package/client/client.js +367 -362
- package/client/client.js.map +1 -1
- package/lib/http/catalog.js +4 -4
- package/lib/http/diagnostics.js +4 -4
- package/lib/http/install.js +13 -13
- package/lib/http/npm-release-age.js +3 -3
- package/lib/http/shared.js +1 -1
- package/lib/services/host-version.js +2 -2
- package/lib/services/install/dsh-compat.js +1 -1
- package/lib/services/install/npm-resolve.js +1 -1
- package/lib/services/install/operation-log.js +3 -3
- package/lib/services/install/preflight.js +1 -1
- package/lib/services/install/registry-fallback.js +3 -3
- package/lib/services/install/release-target.js +2 -2
- package/lib/services/install/task-queue.js +35 -20
- package/lib/services/loader.js +8 -8
- package/lib/services/npm-release-age.js +8 -8
- package/lib/services/probe.js +12 -12
- package/lib/services/profile/installed-versions.js +2 -2
- package/lib/services/profile/profile.js +2 -2
- package/lib/types/services/host-version.d.ts +1 -1
- package/lib/types/services/install/install-types.d.ts +5 -5
- package/lib/types/services/install/operation-log.d.ts +1 -1
- package/lib/types/services/install/preflight.d.ts +1 -1
- package/lib/types/services/install/registry-fallback.d.ts +1 -1
- package/lib/types/services/install/release-target.d.ts +1 -1
- package/lib/types/services/install/task-queue.d.ts +10 -7
- package/lib/types/services/loader.d.ts +5 -5
- package/lib/types/services/npm-release-age.d.ts +5 -5
- package/lib/types/services/probe.d.ts +4 -4
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +29 -29
- package/src/client/components/catalog/CatalogList.tsx +1 -1
- package/src/client/components/catalog/PluginCard.tsx +3 -3
- package/src/client/components/modals/AboutModal.tsx +2 -2
- package/src/client/components/modals/ConfirmDialog.tsx +2 -2
- package/src/client/components/modals/HubUpdateModal.tsx +3 -3
- package/src/client/components/modals/InstalledDetailModal.tsx +1 -1
- package/src/client/components/modals/LogsPathModal.tsx +1 -1
- package/src/client/components/modals/NotificationsModal.tsx +2 -2
- package/src/client/components/modals/modals.tsx +10 -10
- package/src/client/components/views/CustomInstallView.tsx +2 -2
- package/src/client/components/views/InstalledView.tsx +2 -2
- package/src/client/components/views/SettingsView.tsx +3 -3
- package/src/client/data/catalog.ts +1 -1
- package/src/client/data/host.ts +2 -2
- package/src/client/data/hub.ts +3 -3
- package/src/client/hooks/useCatalog.ts +14 -14
- package/src/client/hooks/useSettings.ts +2 -2
- package/src/client/hooks/useTaskQueue.ts +30 -23
- package/src/client/locales.ts +15 -15
- package/src/client/logic/constants.ts +1 -1
- package/src/client/logic/ensure-plugin-css.ts +2 -2
- package/src/client/logic/failures.ts +9 -9
- package/src/client/logic/install-command.ts +1 -2
- package/src/client/logic/installed.ts +1 -1
- package/src/client/logic/release-age.ts +10 -10
- package/src/client/logic/urls.ts +3 -3
- package/src/client/styles/Header.module.css +1 -1
- package/src/client/styles/Modal.module.css +2 -2
- package/src/client/styles/SectionTabs.module.css +1 -1
- package/src/client/styles/tokens.module.css +1 -1
- package/src/client/types.ts +4 -4
- package/src/server/http/catalog.ts +4 -4
- package/src/server/http/diagnostics.ts +4 -4
- package/src/server/http/install.ts +13 -13
- package/src/server/http/npm-release-age.ts +3 -3
- package/src/server/http/shared.ts +1 -1
- package/src/server/services/host-version.ts +2 -2
- package/src/server/services/install/dsh-compat.ts +1 -1
- package/src/server/services/install/install-types.ts +5 -5
- package/src/server/services/install/npm-resolve.ts +1 -1
- package/src/server/services/install/operation-log.ts +3 -3
- package/src/server/services/install/preflight.ts +1 -1
- package/src/server/services/install/registry-fallback.ts +3 -3
- package/src/server/services/install/release-target.ts +2 -2
- package/src/server/services/install/task-queue.ts +40 -26
- package/src/server/services/loader.ts +10 -10
- package/src/server/services/npm-release-age.ts +10 -10
- package/src/server/services/probe.ts +12 -12
- package/src/server/services/profile/installed-versions.ts +2 -2
- package/src/server/services/profile/profile.ts +2 -2
package/src/client/locales.ts
CHANGED
|
@@ -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
|
-
//
|
|
260
|
+
// 结果视图补充:仅当「刷新解决不了」时(热加载失败 / 非 dsh 插件 / 更新)才出现单个「立即重启」;
|
|
261
261
|
// 关闭弹窗即视为「稍后」,不再单独给「稍后重启」按钮
|
|
262
262
|
restartNow: '立即重启',
|
|
263
263
|
restarting: '正在重启…',
|
|
264
264
|
restartHint: '重启会中断正在进行的安装/卸载任务,其进度将丢失。部分插件(如插件市场、顶层 bundle)需重启后才会生效。',
|
|
265
265
|
// 桌面端宿主归应用壳管、插件重启不了它:点「立即重启」时改提示用户退出重开
|
|
266
266
|
toastRestartDesktop: '桌面端的宿主由应用管理,请在应用菜单(或托盘)选择退出后重新打开,重启后生效',
|
|
267
|
-
//
|
|
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
|
-
//
|
|
495
|
-
//
|
|
494
|
+
// 冷却期未到:新版本发布未满 24 小时,此时安装会被 pnpm 不提示地回退(或直接拒绝),
|
|
495
|
+
// 给出确切可重试时刻,而不是让徽标悄悄消失、让用户以为已经是最新
|
|
496
496
|
updatePending: '暂缓到 {time} 后可更新',
|
|
497
|
-
updatePendingHint: '
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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
|
|
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
|
-
//
|
|
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
|
-
// 与本次要装的插件无关 ——
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
7
|
-
* 故发布太新的版本不该提示更新。Hub
|
|
6
|
+
* 发布冷却期的时间判定(纯函数):pnpm 会不提示地回退发布还不满 24 小时的版本,
|
|
7
|
+
* 故发布太新的版本不该提示更新。Hub 自身更新与插件更新共用同一规则。
|
|
8
8
|
*/
|
|
9
9
|
import type { HubUpdateInfo, HubUpdateState, HubUpdateVersion } from '../types.ts'
|
|
10
10
|
|
|
11
|
-
/** pnpm
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 } {
|
package/src/client/logic/urls.ts
CHANGED
|
@@ -228,7 +228,7 @@ export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | nu
|
|
|
228
228
|
* 错误摘要,供插件中心维护者分诊 —— 不再把环境问题/插件中心自身问题误投给插件作者。
|
|
229
229
|
*/
|
|
230
230
|
export type HubIssueVariant =
|
|
231
|
-
/**
|
|
231
|
+
/** 缺省:安装器主动把失败送到插件中心分诊(未分类时的默认去向) */
|
|
232
232
|
| 'triage'
|
|
233
233
|
/** 从判定由插件中心自己产出的分支(插件打包问题 / allowBuilds 拦截)的次要入口提交:用户怀疑是误报 */
|
|
234
234
|
| 'misjudge'
|
|
@@ -249,7 +249,7 @@ export function hubIssueUrl(
|
|
|
249
249
|
const zhWord = actionWordZh(action)
|
|
250
250
|
const repo = pluginRepo && pluginRepo.trim() !== '' ? pluginRepo.trim() : 'unknown'
|
|
251
251
|
const title = variant === 'misjudge'
|
|
252
|
-
//
|
|
252
|
+
// 标题就点明「可能是误判」,维护者一眼能区分它和未分类时的默认去向
|
|
253
253
|
? `[dsh-plugin-hub] possible misjudgment: ${repo}`
|
|
254
254
|
: `[dsh-plugin-hub] ${reasonTitleOf(kind, action)}: ${repo}`
|
|
255
255
|
const code = coreErrorCode(message)
|
|
@@ -264,7 +264,7 @@ export function hubIssueUrl(
|
|
|
264
264
|
...(variant === 'misjudge'
|
|
265
265
|
// 这些分支的判据由插件中心自己产出、或掺了我们的动作(自动放行 allowBuilds)——
|
|
266
266
|
// 一旦我们自己误报,用户就被送去作者仓库、我们永远收不到反馈。
|
|
267
|
-
//
|
|
267
|
+
// 正文写清它来自哪条分支、判据是谁给的,维护者才不会把它当成未分类时的默认去向。
|
|
268
268
|
? [
|
|
269
269
|
'- Filed from: a branch that normally routes to the plugin repo — filed here because that verdict may be our own misjudgment.',
|
|
270
270
|
'- Source of that verdict: produced by dsh-plugin-hub\'s own check or its auto-allow retry, not by plain pnpm output.',
|
|
@@ -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
|
|
108
|
+
/* 通知铃铛按钮:位于「设置」tab 后边、靠右对齐(margin-left:auto 占满 tab 剩余空间),
|
|
109
109
|
在 38px tab 栏里垂直居中;常态透明底只显铃铛轮廓,hover 浅灰,不抢 tab 视觉层级 */
|
|
110
110
|
.noticeBtn {
|
|
111
111
|
position: relative;
|
package/src/client/types.ts
CHANGED
|
@@ -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`
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
127
|
-
//
|
|
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
|
-
//
|
|
186
|
-
//
|
|
185
|
+
// 新安装锁定「已过发布冷却期」的最高稳定版:pnpm 对发布还不满 24 小时的版本会不提示地回退,
|
|
186
|
+
// 锁定后装到的就是预期版本。更新已有目标时客户端已按冷却期筛选,继续走 @latest。
|
|
187
187
|
let installSpec = pinVersion !== '' ? `${target}@${pinVersion}` : undefined
|
|
188
188
|
if (installSpec === undefined && already === null) {
|
|
189
|
-
//
|
|
190
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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)
|