dsh-plugin 1.6.12 → 2.0.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.
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/client/client.js +615 -741
- package/client/client.js.map +1 -1
- package/lib/http/catalog.js +2 -3
- package/lib/http/diagnostics.js +6 -7
- package/lib/http/install.js +47 -213
- package/lib/http/logs.js +1 -2
- package/lib/http/npm-release-age.js +42 -0
- package/lib/http/routes.js +8 -7
- package/lib/http/settings.js +2 -2
- package/lib/http/shared.js +2 -2
- package/lib/http/task.js +1 -2
- package/lib/index.js +5 -6
- package/lib/services/install/install-types.js +4 -4
- package/lib/services/install/install.js +1 -1
- package/lib/services/install/npm-check.js +3 -2
- package/lib/services/install/npm-resolve.js +9 -96
- package/lib/services/install/package-entry.js +6 -11
- package/lib/services/install/registry-check.js +2 -2
- package/lib/services/install/release-target.js +8 -9
- package/lib/services/install/task-queue.js +40 -48
- package/lib/services/npm-release-age.js +119 -0
- package/lib/services/probe.js +4 -4
- package/lib/services/profile/installed-versions.js +15 -21
- package/lib/services/profile/profile.js +16 -44
- package/lib/services/profile/progress.js +8 -11
- package/lib/services/settings.js +2 -5
- package/lib/types/http/catalog.d.ts +1 -1
- package/lib/types/http/install.d.ts +2 -2
- package/lib/types/http/npm-release-age.d.ts +2 -0
- package/lib/types/http/routes.d.ts +10 -12
- package/lib/types/http/shared.d.ts +5 -6
- package/lib/types/index.d.ts +7 -8
- package/lib/types/services/install/install-types.d.ts +12 -13
- package/lib/types/services/install/install.d.ts +1 -1
- package/lib/types/services/install/npm-resolve.d.ts +4 -16
- package/lib/types/services/install/package-entry.d.ts +6 -11
- package/lib/types/services/install/release-target.d.ts +7 -8
- package/lib/types/services/install/task-queue.d.ts +25 -30
- package/lib/types/services/npm-release-age.d.ts +15 -0
- package/lib/types/services/probe.d.ts +4 -4
- package/lib/types/services/profile/installed-versions.d.ts +5 -7
- package/lib/types/services/profile/profile.d.ts +14 -18
- package/lib/types/services/profile/progress.d.ts +7 -10
- package/lib/types/services/settings.d.ts +0 -4
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +17 -22
- package/src/client/components/catalog/CatalogList.tsx +2 -4
- package/src/client/components/catalog/PluginCard.tsx +2 -3
- package/src/client/components/layout/CatalogControls.tsx +5 -6
- package/src/client/components/layout/CatalogHeader.tsx +1 -2
- package/src/client/components/layout/CategoryTabs.tsx +2 -3
- package/src/client/components/layout/SectionTabs.tsx +3 -5
- package/src/client/components/modals/ConfirmDialog.tsx +4 -6
- package/src/client/components/modals/InstalledDetailModal.tsx +3 -6
- package/src/client/components/modals/LogsModal.tsx +7 -12
- package/src/client/components/modals/NotificationsModal.tsx +35 -16
- package/src/client/components/modals/ProgressView.tsx +3 -4
- package/src/client/components/modals/modals.tsx +35 -15
- package/src/client/components/ui/Dropdown.tsx +4 -6
- package/src/client/components/ui/Toggle.tsx +2 -3
- package/src/client/components/ui/icons.tsx +1 -2
- package/src/client/components/views/CustomInstallView.tsx +20 -178
- package/src/client/components/views/DiagnosticsView.tsx +3 -6
- package/src/client/components/views/InstalledView.tsx +6 -11
- package/src/client/components/views/MarketView.tsx +2 -4
- package/src/client/components/views/SettingsView.tsx +9 -32
- package/src/client/css-modules.d.ts +1 -1
- package/src/client/data/catalog.ts +2 -3
- package/src/client/data/host.ts +26 -3
- package/src/client/data/hub.ts +2 -3
- package/src/client/hooks/useCatalog.ts +79 -24
- package/src/client/hooks/useIncrementalList.ts +3 -5
- package/src/client/hooks/useLanguage.ts +4 -6
- package/src/client/hooks/useSettings.ts +4 -11
- package/src/client/hooks/useTaskQueue.ts +36 -42
- package/src/client/index.tsx +2 -3
- package/src/client/locales.ts +75 -114
- package/src/client/logic/constants.ts +1 -2
- package/src/client/logic/ensure-plugin-css.ts +8 -12
- package/src/client/logic/failures.ts +16 -18
- package/src/client/logic/format.ts +1 -1
- package/src/client/logic/install-command.ts +14 -35
- package/src/client/logic/installed.ts +15 -10
- package/src/client/logic/normalize.ts +3 -3
- package/src/client/logic/release-age.ts +19 -0
- package/src/client/logic/urls.ts +20 -20
- package/src/client/styles/CustomInstallView.module.css +3 -30
- package/src/client/styles/DiagnosticsView.module.css +3 -5
- package/src/client/styles/Dropdown.module.css +2 -4
- package/src/client/styles/Header.module.css +6 -7
- package/src/client/styles/InstalledView.module.css +4 -7
- package/src/client/styles/List.module.css +4 -5
- package/src/client/styles/LogsView.module.css +2 -3
- package/src/client/styles/Modal.module.css +4 -5
- package/src/client/styles/SectionTabs.module.css +2 -4
- package/src/client/styles/SettingsView.module.css +3 -5
- package/src/client/styles/Toggle.module.css +2 -4
- package/src/client/styles/tokens.module.css +14 -18
- package/src/client/types.ts +11 -11
- package/src/server/http/catalog.ts +2 -3
- package/src/server/http/diagnostics.ts +6 -7
- package/src/server/http/install.ts +47 -208
- package/src/server/http/logs.ts +1 -2
- package/src/server/http/npm-release-age.ts +42 -0
- package/src/server/http/routes.ts +12 -12
- package/src/server/http/settings.ts +2 -2
- package/src/server/http/shared.ts +5 -6
- package/src/server/http/task.ts +1 -2
- package/src/server/index.ts +7 -8
- package/src/server/services/install/install-types.ts +12 -13
- package/src/server/services/install/install.ts +0 -1
- package/src/server/services/install/npm-check.ts +3 -2
- package/src/server/services/install/npm-resolve.ts +9 -99
- package/src/server/services/install/package-entry.ts +6 -11
- package/src/server/services/install/registry-check.ts +2 -2
- package/src/server/services/install/release-target.ts +8 -9
- package/src/server/services/install/task-queue.ts +47 -54
- package/src/server/services/npm-release-age.ts +107 -0
- package/src/server/services/probe.ts +4 -4
- package/src/server/services/profile/installed-versions.ts +16 -22
- package/src/server/services/profile/profile.ts +16 -42
- package/src/server/services/profile/progress.ts +8 -11
- package/src/server/services/settings.ts +2 -9
- package/lib/services/install/preflight.js +0 -178
- package/lib/types/services/install/preflight.d.ts +0 -23
- package/src/server/services/install/preflight.ts +0 -184
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* npm 包发布时间查询路由:按已安装插件的 npm 包名按需查询最新版本的发布时间,
|
|
7
|
+
* 供客户端判定 pnpm 24h 供应链门禁(发布未满门槛的版本不提示更新)。
|
|
8
|
+
*/
|
|
9
|
+
import { systemProxy } from '../services/probe.ts'
|
|
10
|
+
import { lookupReleaseAge, parseReleaseAgeRequest } from '../services/npm-release-age.ts'
|
|
11
|
+
import { loadSettings } from '../services/settings.ts'
|
|
12
|
+
import { readJsonBody, requireTrustedPost, sendJson, type WebRoute, type WebServerService } from './shared.ts'
|
|
13
|
+
|
|
14
|
+
export function registerNpmReleaseAgeRoutes(webServer: WebServerService, profile: string): WebRoute[] {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
kind: 'exact',
|
|
18
|
+
path: '/dsh-plugin-hub/npm-release-age',
|
|
19
|
+
handler: async (request, response) => {
|
|
20
|
+
if (!requireTrustedPost(request, response)) return
|
|
21
|
+
try {
|
|
22
|
+
const packages = parseReleaseAgeRequest(await readJsonBody(request))
|
|
23
|
+
if (packages === null) {
|
|
24
|
+
sendJson(response, 400, { error: 'invalid package list' })
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
const settings = loadSettings(profile)
|
|
28
|
+
// 代理口径与安装/目录一致:设置里的代理 → 系统代理 → 环境变量 → 直连
|
|
29
|
+
const proxy = settings.proxy !== ''
|
|
30
|
+
? settings.proxy
|
|
31
|
+
: (systemProxy() ?? process.env.HTTPS_PROXY ?? process.env.https_proxy ?? '')
|
|
32
|
+
// 并发查询:单包各自带超时且失败即 null,整体不因任一包失败而失败
|
|
33
|
+
const results = await Promise.all(packages.map(async (name) =>
|
|
34
|
+
[name, await lookupReleaseAge(name, settings.npmRegistry, proxy)] as const))
|
|
35
|
+
sendJson(response, 200, { packages: Object.fromEntries(results) })
|
|
36
|
+
} catch (error) {
|
|
37
|
+
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) })
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* install
|
|
9
|
-
* (see services/install/install.ts) and reports the captured result back.
|
|
6
|
+
* 本地 HTTP 路由:把真实安装能力暴露给应用内的插件中心 UI。客户端请求同源
|
|
7
|
+
* `/dsh-plugin-hub/*` 端点;安装处理校验目标后 spawn 官方 dsh CLI(见
|
|
8
|
+
* services/install/install.ts)并回传捕获的结果。
|
|
10
9
|
*/
|
|
11
10
|
import { type LoaderHandle } from '../services/install/install.ts'
|
|
12
11
|
import { appendLog } from '../services/log.ts'
|
|
@@ -17,13 +16,13 @@ import { registerSettingsRoutes } from './settings.ts'
|
|
|
17
16
|
import { registerLogsRoutes } from './logs.ts'
|
|
18
17
|
import { registerDiagnosticsRoutes } from './diagnostics.ts'
|
|
19
18
|
import { registerTaskRoutes } from './task.ts'
|
|
19
|
+
import { registerNpmReleaseAgeRoutes } from './npm-release-age.ts'
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @param webServer - DSH web server
|
|
24
|
-
* @param profile - profile
|
|
25
|
-
* @param loader -
|
|
26
|
-
* immediately so the page survives a refresh without a host restart.
|
|
22
|
+
* 在宿主 web server 上注册插件中心 API 并返回 dispose 函数。
|
|
23
|
+
* @param webServer - DSH web server 服务。
|
|
24
|
+
* @param profile - 拥有插件变更权限的 profile。
|
|
25
|
+
* @param loader - 运行中 loader(可选):让卸载立即移除条目,页面刷新即生效、无需重启宿主。
|
|
27
26
|
*/
|
|
28
27
|
export function mountPluginHubRoutes(webServer: WebServerService, profile: string, loader?: LoaderHandle): () => void {
|
|
29
28
|
if (!PROFILE_RE.test(profile)) throw new Error(`invalid profile name: ${profile}`)
|
|
@@ -43,6 +42,7 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
43
42
|
...registerLogsRoutes(webServer, profile),
|
|
44
43
|
...registerDiagnosticsRoutes(webServer, profile),
|
|
45
44
|
...registerTaskRoutes(webServer, profile, loader),
|
|
45
|
+
...registerNpmReleaseAgeRoutes(webServer, profile),
|
|
46
46
|
]
|
|
47
47
|
const disposers = routes.map((route) => webServer.register(route))
|
|
48
48
|
return () => {
|
|
@@ -50,9 +50,9 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
53
|
+
/** 与客户端路由文档共用的 profile 解析。 */
|
|
54
54
|
export { readProfileArg } from '../services/install/install.ts'
|
|
55
|
-
/**
|
|
55
|
+
/** 转出以便宿主入口(index.ts)继续从本模块引入。 */
|
|
56
56
|
export type { WebServerService } from './shared.ts'
|
|
57
|
-
/**
|
|
57
|
+
/** 转出以便 tests/routes.test.ts 继续从本模块引入。 */
|
|
58
58
|
export { isDesktopHost, isSameOrigin } from './shared.ts'
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 设置路由:读写插件中心设置,以及恢复默认。
|
|
7
7
|
*/
|
|
8
8
|
import { mkdirSync } from 'node:fs'
|
|
9
9
|
import { dirname } from 'node:path'
|
|
@@ -28,7 +28,7 @@ export function registerSettingsRoutes(webServer: WebServerService, profile: str
|
|
|
28
28
|
// 白名单字段:只接受已知设置项,防客户端把任意键写进文件
|
|
29
29
|
const patch: Record<string, unknown> = {}
|
|
30
30
|
const source = typeof body === 'object' && body !== null ? body as Record<string, unknown> : {}
|
|
31
|
-
for (const key of ['checkUpdatesOnStart', 'enableNpmInstall', '
|
|
31
|
+
for (const key of ['checkUpdatesOnStart', 'enableNpmInstall', 'npmRegistry', 'proxy', 'logPath'] as const) {
|
|
32
32
|
if (source[key] !== undefined) patch[key] = source[key]
|
|
33
33
|
}
|
|
34
34
|
// 日志位置:非空时预建目录验证可写,失败直接 400 拦截(默认位置永远合法,空串 = 回默认)
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* route modules in this directory.
|
|
6
|
+
* 共享 HTTP 工具:路由/服务类型、请求校验助手,以及守卫每个 POST 变更的同源校验。
|
|
7
|
+
* 由本目录其它路由模块引入。
|
|
9
8
|
*/
|
|
10
9
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
11
10
|
|
|
@@ -97,7 +96,7 @@ export interface WebRoute {
|
|
|
97
96
|
handler: (request: IncomingMessage, response: ServerResponse) => void | Promise<void>
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
/**
|
|
99
|
+
/** 本插件用到的宿主 web-server 服务子集。 */
|
|
101
100
|
export interface WebServerService {
|
|
102
101
|
register(route: WebRoute): () => void
|
|
103
102
|
}
|
|
@@ -135,8 +134,8 @@ function loopbackAuthority(authority: string): string | null {
|
|
|
135
134
|
}
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
/** POST
|
|
139
|
-
*
|
|
137
|
+
/** POST 变更只接受来自本地 web server 来源或桌面宿主页面。转出供测试(tests/routes.test.ts)使用 ——
|
|
138
|
+
* 来源校验就是全部的 CSRF 防御。 */
|
|
140
139
|
export function isSameOrigin(request: IncomingMessage): boolean {
|
|
141
140
|
// 服务只绑定本机回环地址:Host 不是回环(局域网 IP、被改写的域名)一律拒绝,
|
|
142
141
|
// 顺带挡掉 DNS rebinding(Host 是攻击者域名,即便解析到 127.0.0.1)。
|
package/src/server/http/task.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* the installed-plugin inventory the client uses to reconcile its list.
|
|
6
|
+
* 任务/杂项路由:任务轮询、宿主重启、loader 条目调试,以及客户端用于对账列表的已装插件清单。
|
|
8
7
|
*/
|
|
9
8
|
import { spawn } from 'node:child_process'
|
|
10
9
|
import { join } from 'node:path'
|
package/src/server/index.ts
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
* dsh-plugin
|
|
7
|
-
*
|
|
8
|
-
* these same-origin routes to perform real installs from inside the app.
|
|
6
|
+
* dsh-plugin 宿主入口:宿主 webServer 服务就绪后挂载插件中心的 HTTP 路由。
|
|
7
|
+
* 浏览器 bundle(src/client)通过这些同源路由在应用内执行真正的安装。
|
|
9
8
|
*/
|
|
10
9
|
import { readProfileArg, type LoaderHandle } from './services/install/install.ts'
|
|
11
10
|
import { mountPluginHubRoutes, type WebServerService } from './http/routes.ts'
|
|
@@ -13,11 +12,11 @@ import { mountPluginHubRoutes, type WebServerService } from './http/routes.ts'
|
|
|
13
12
|
export const name = 'dsh-plugin'
|
|
14
13
|
|
|
15
14
|
export interface Config {
|
|
16
|
-
/** DSH profile
|
|
15
|
+
/** 拥有插件变更权限的 DSH profile,缺省用启动时的 profile。 */
|
|
17
16
|
profile?: string
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
/**
|
|
19
|
+
/** 本插件用到的 cordis 宿主上下文子集。 */
|
|
21
20
|
interface HostContext {
|
|
22
21
|
webServer: WebServerService
|
|
23
22
|
/** 运行中 loader:卸载成功后主动移除条目、立即生效(宿主未提供时卸载仍需重启) */
|
|
@@ -27,9 +26,9 @@ interface HostContext {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
|
-
*
|
|
31
|
-
* @param ctx - cordis
|
|
32
|
-
* @param config -
|
|
29
|
+
* webServer 服务就绪后挂载插件中心路由。
|
|
30
|
+
* @param ctx - cordis 宿主上下文。
|
|
31
|
+
* @param config - 可选的 profile 覆盖。
|
|
33
32
|
*/
|
|
34
33
|
export function apply(ctx: HostContext, config: Config = {}): void {
|
|
35
34
|
const profile = config.profile ?? readProfileArg('web')
|
|
@@ -17,27 +17,26 @@ export interface InstallResult {
|
|
|
17
17
|
|
|
18
18
|
export interface InstallTask {
|
|
19
19
|
id: number
|
|
20
|
-
/**
|
|
20
|
+
/** 操作目标:npm 包名(安装、更新、卸载一致),冲突/恢复时需要展示给用户 */
|
|
21
21
|
target: string
|
|
22
22
|
/** 展示用目标(owner/repo):npm 安装时 target 是包名,前端恢复用此字段显示仓库名,保持用户无感知 */
|
|
23
23
|
displayTarget?: string
|
|
24
|
-
/**
|
|
25
|
-
* 失败提 Issue
|
|
26
|
-
* 仅凭仓库名猜不到,作者看到我们查过/试过的命令就能直接指认)。 */
|
|
24
|
+
/** 尝试过的安装方式(实际执行过的 CLI 命令,按先后顺序):
|
|
25
|
+
* 失败提 Issue 时如实贴给作者,作者据此判断我们真正执行了什么。 */
|
|
27
26
|
attempts: string[]
|
|
28
27
|
/** 全局 npm 安装(官方 `npm install -g <pkgs>`):非空时任务执行全局 npm 安装,不进任何 profile */
|
|
29
28
|
globalNpm?: string[]
|
|
30
29
|
action: 'add' | 'remove' | 'update'
|
|
31
|
-
/**
|
|
30
|
+
/** 自定义安装入口渠道(NPM 包卡片):
|
|
32
31
|
* 任务输出行与系统日志据此溯源「从哪个入口发起」;目录插件安装无此字段。 */
|
|
33
|
-
installChannel?: 'npm'
|
|
32
|
+
installChannel?: 'npm'
|
|
34
33
|
/** 队列语义:pending 排队中 / running 执行中 / done / failed / cancelled(用户取消) */
|
|
35
34
|
status: 'pending' | 'running' | 'done' | 'failed' | 'cancelled'
|
|
36
35
|
timedOut: boolean
|
|
37
36
|
exitCode: number | null
|
|
38
37
|
/** 0-100 估算进度:解析 pnpm 的 `Progress: resolved…` 输出,阶段行兜底 */
|
|
39
38
|
progress: number
|
|
40
|
-
/**
|
|
39
|
+
/** 输出行最新在前(消费方展示尾部)。 */
|
|
41
40
|
lines: string[]
|
|
42
41
|
/** 完成后是否仍需宿主重启才生效:仅「热挂载失败 / 非 dsh 插件 / 更新(ESM 缓存取不到新代码)/
|
|
43
42
|
* 无 loader」等刷新解决不了的兜底场景为 true(弹窗给「立即重启」);其余一律 false。 */
|
|
@@ -70,7 +69,7 @@ export interface QueueItem {
|
|
|
70
69
|
timeoutMs?: number
|
|
71
70
|
env?: NodeJS.ProcessEnv
|
|
72
71
|
globalNpm?: string[]
|
|
73
|
-
installChannel?: 'npm'
|
|
72
|
+
installChannel?: 'npm'
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
|
|
@@ -79,7 +78,7 @@ export interface QueueItem {
|
|
|
79
78
|
* 它自然清空,正好等于「重启后提醒消失」的语义,无需任何重启探测。
|
|
80
79
|
*/
|
|
81
80
|
export interface PendingRestart {
|
|
82
|
-
/**
|
|
81
|
+
/** 安装目标(与任务一致;卸载时为展示用的 owner/repo) */
|
|
83
82
|
target: string
|
|
84
83
|
/** 待重启语义:install 装完等挂载生效 / uninstall 卸完等清理 loader 残留 */
|
|
85
84
|
kind: 'install' | 'uninstall'
|
|
@@ -87,13 +86,13 @@ export interface PendingRestart {
|
|
|
87
86
|
at: number
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
/**
|
|
89
|
+
/** 目录 GitHub 仓库身份(`owner/repo`)的语法。 */
|
|
91
90
|
export const REPO_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/
|
|
92
|
-
/**
|
|
91
|
+
/** npm 包名的语法(用于卸载目标)。 */
|
|
93
92
|
export const PACKAGE_RE = /^(?:@[a-z0-9._-]+\/)?[A-Za-z0-9._-]+$/
|
|
94
93
|
/** 捕获输出上限:stdout/stderr 各保留尾部 N 字节。 */
|
|
95
94
|
export const CAPTURE_LIMIT_BYTES = 64 * 1024
|
|
96
|
-
/**
|
|
95
|
+
/** 每个任务保留的最大输出行数(最新优先)。 */
|
|
97
96
|
export const MAX_TASK_LINES = 200
|
|
98
|
-
/**
|
|
97
|
+
/** 最多跟踪的任务数;超出时先丢弃最早结束的任务。 */
|
|
99
98
|
export const MAX_TASKS = 50
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 本机 npm 环境检测:判断一次安装失败是否源于「本机 npm 版本过低」。
|
|
7
7
|
*
|
|
8
|
-
* 背景:pnpm
|
|
9
|
-
*
|
|
8
|
+
* 背景:pnpm 在带 `prepare` 构建脚本的依赖上会调 npm 装依赖(npm 包同样可能带原生
|
|
9
|
+
* 依赖或 git 依赖),npm arborist 在解 peer 依赖时有已知内部崩溃 ——
|
|
10
|
+
* `Cannot read properties of null (reading 'edgesOut')`。这是 npm 自身缺陷,
|
|
10
11
|
* 该报错会被 pnpm 包进 ERR_PNPM_PREPARE_PACKAGE 输出,不能按插件打包问题引导用户提 Issue。
|
|
11
12
|
*
|
|
12
13
|
* 这里的 `npmTooLowMarker` 是纯函数(输出文本 + 环境 → 标记行或 null),不触碰任务/队列,
|
|
@@ -3,50 +3,18 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
* npm
|
|
7
|
-
*
|
|
6
|
+
* npm 包校验:确认某个 npm 包确实属于该 GitHub 仓库、且声明为 DSH 插件
|
|
7
|
+
* (包元数据里的 repository 指向该仓库,并带 `dsh-plugin` 关键词或 `dsh` manifest)。
|
|
8
8
|
*
|
|
9
9
|
* 这是通用机制,不针对任何具体插件:只要作者发布了 repository 指向该
|
|
10
|
-
* GitHub 仓库的 npm 包(scope 名与 GitHub
|
|
11
|
-
* 从而把 git 分发不完整的插件切换到 npm 通道安装。命中结果带内存缓存,
|
|
12
|
-
* 避免同一会话内重复查询 registry。
|
|
10
|
+
* GitHub 仓库的 npm 包(scope 名与 GitHub 用户名是否一致无所谓),就能命中。
|
|
13
11
|
*/
|
|
14
12
|
import { get } from 'node:https'
|
|
15
13
|
import { githubRepoOf } from '../profile/profile.ts'
|
|
16
14
|
import { finalUrl } from '../probe.ts'
|
|
17
15
|
|
|
18
|
-
/**
|
|
19
|
-
const cache = new Map<string, string | null>()
|
|
20
|
-
|
|
21
|
-
/** 单次 registry 查询超时(ms):慢网络下失败返回 null,不阻塞安装流程。 */
|
|
16
|
+
/** 单次 registry 查询超时(ms):慢网络下失败返回 false,不阻塞安装流程。 */
|
|
22
17
|
const REQUEST_TIMEOUT_MS = 8000
|
|
23
|
-
/** npm search 是相关性搜索而非 repository 精确索引;拉满单页结果后再本地做严格 repo 过滤。 */
|
|
24
|
-
const SEARCH_RESULT_LIMIT = 250
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 反查 repo(`owner/repo`)对应的官方 npm 包名;未命中、网络异常或超时返回 null。
|
|
28
|
-
* 返回 null 不代表仓库一定没有 npm 包,只代表本次未能确认 —— 调用方应保留
|
|
29
|
-
* 原有错误路径,反查只是额外的一次尝试。只有「已确认」的结果(包名,或 200
|
|
30
|
-
* 响应下确认无匹配)会进缓存;网络异常/超时/解析失败不缓存,同一会话内下次
|
|
31
|
-
* 安装仍会重试反查,避免一次瞬时故障把整个会话锁死在 git 通道。
|
|
32
|
-
* registry 参数:npm 镜像源地址,空串 = 官方源;与安装通道吃同一 registry,
|
|
33
|
-
* 保证「配置了镜像」时反查和安装走同一个源(镜像节点同步完整时结果一致)。
|
|
34
|
-
* proxy 参数:GitHub 改名跳转探测走的代理(与安装/诊断同一口径)。
|
|
35
|
-
* 慢网络下失败不阻塞安装。
|
|
36
|
-
*/
|
|
37
|
-
export async function resolveNpmPackage(repo: string, registry = '', proxy = ''): Promise<string | null> {
|
|
38
|
-
const key = repo.toLowerCase()
|
|
39
|
-
if (cache.has(key)) return cache.get(key) ?? null
|
|
40
|
-
let found = await searchRepo(repo, registry)
|
|
41
|
-
// 仓库改名后 npm 包的 repository 已指向新名,按旧名反查不到:跟随一次 GitHub 的
|
|
42
|
-
// 改名跳转拿到规范名再查一遍(旧地址本身仍可用,只是仓库名变了)。
|
|
43
|
-
if (found === null) {
|
|
44
|
-
const canonical = await canonicalRepo(repo, proxy)
|
|
45
|
-
if (canonical !== '' && canonical.toLowerCase() !== key) found = await searchRepo(canonical, registry)
|
|
46
|
-
}
|
|
47
|
-
if (found !== undefined) cache.set(key, found)
|
|
48
|
-
return found ?? null
|
|
49
|
-
}
|
|
50
18
|
|
|
51
19
|
type NpmSearchPackage = {
|
|
52
20
|
name?: unknown
|
|
@@ -56,16 +24,7 @@ type NpmSearchPackage = {
|
|
|
56
24
|
dsh?: unknown
|
|
57
25
|
}
|
|
58
26
|
|
|
59
|
-
|
|
60
|
-
type NpmSearchResponse = { objects?: NpmSearchObject[] }
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Pick the installable npm package for one GitHub repository. npm search is a
|
|
64
|
-
* relevance-ranked text search, not an exact repository index, so first filter
|
|
65
|
-
* by repository URL. If a monorepo publishes both a CLI/helper package and a
|
|
66
|
-
* DSH plugin, prefer the package that explicitly advertises the `dsh-plugin`
|
|
67
|
-
* keyword instead of whichever result npm happened to rank first.
|
|
68
|
-
*/
|
|
27
|
+
/** 从包元数据取 repository 地址:字符串或 `{ url }` 对象,缺省回退 search 结果的 links.repository。 */
|
|
69
28
|
function repositoryUrlOf(pkg: NpmSearchPackage): string {
|
|
70
29
|
if (typeof pkg.repository === 'string') return pkg.repository
|
|
71
30
|
if (pkg.repository !== null && typeof pkg.repository === 'object') {
|
|
@@ -83,6 +42,10 @@ export function isDshPackageMetadataForRepo(pkg: NpmSearchPackage, repo: string)
|
|
|
83
42
|
return keywordMarked || manifestMarked
|
|
84
43
|
}
|
|
85
44
|
|
|
45
|
+
/**
|
|
46
|
+
* 校验包名对应的 npm 包确实属于该仓库且声明为 DSH 插件:取 registry 上的 `latest`
|
|
47
|
+
* 元数据比对 repository 与插件标记。网络异常、超时、非 200 一律返回 false,不阻塞调用方。
|
|
48
|
+
*/
|
|
86
49
|
export function isDshNpmPackageForRepo(packageName: string, repo: string, registry = '', proxy = ''): Promise<boolean> {
|
|
87
50
|
if (packageName === '' || repo === '') return Promise.resolve(false)
|
|
88
51
|
const base = registry === '' ? 'https://registry.npmjs.org' : registry.replace(/\/+$/, '')
|
|
@@ -130,56 +93,3 @@ async function canonicalRepo(repo: string, proxy: string): Promise<string> {
|
|
|
130
93
|
const canonical = final === null ? null : githubRepoOf(final)
|
|
131
94
|
return canonical !== null && canonical.toLowerCase() !== key ? canonical : ''
|
|
132
95
|
}
|
|
133
|
-
|
|
134
|
-
function matchingPackagesForRepo(objects: NpmSearchObject[], repo: string): Array<{ name: string; isDshPlugin: boolean }> {
|
|
135
|
-
const matches: Array<{ name: string; isDshPlugin: boolean }> = []
|
|
136
|
-
|
|
137
|
-
for (const obj of objects) {
|
|
138
|
-
const pkg = obj.package
|
|
139
|
-
if (typeof pkg?.name !== 'string' || pkg.name === '') continue
|
|
140
|
-
const repoUrl = repositoryUrlOf(pkg)
|
|
141
|
-
if (githubRepoOf(repoUrl)?.toLowerCase() !== repo.toLowerCase()) continue
|
|
142
|
-
const keywords = Array.isArray(pkg.keywords) ? pkg.keywords : []
|
|
143
|
-
matches.push({
|
|
144
|
-
name: pkg.name,
|
|
145
|
-
isDshPlugin: keywords.some((keyword) => typeof keyword === 'string' && keyword.toLowerCase() === 'dsh-plugin'),
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return matches
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function selectNpmPackageForRepo(objects: NpmSearchObject[], repo: string): string | null {
|
|
153
|
-
const matches = matchingPackagesForRepo(objects, repo)
|
|
154
|
-
return matches.find((candidate) => candidate.isDshPlugin)?.name ?? matches[0]?.name ?? null
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** 用 npm search 接口按 repository 地址反查,并做铁证校验(包元数据必须指向该仓库)。
|
|
158
|
-
* 返回:包名 = 命中;null = 200 响应下确认无匹配;undefined = 本次未能确认(网络/超时/解析失败)。 */
|
|
159
|
-
function searchRepo(repo: string, registry: string): Promise<string | null | undefined> {
|
|
160
|
-
const [owner, name] = repo.split('/')
|
|
161
|
-
if (owner === undefined || name === undefined || name === '') return Promise.resolve(null)
|
|
162
|
-
const base = registry === '' ? 'https://registry.npmjs.org' : registry.replace(/\/+$/, '')
|
|
163
|
-
const url = `${base}/-/v1/search?text=${encodeURIComponent(`repository:${owner}/${name}`)}&size=${SEARCH_RESULT_LIMIT}`
|
|
164
|
-
return new Promise((resolve) => {
|
|
165
|
-
const req = get(url, { timeout: REQUEST_TIMEOUT_MS }, (res) => {
|
|
166
|
-
if (res.statusCode !== 200) {
|
|
167
|
-
res.resume()
|
|
168
|
-
resolve(undefined)
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
let body = ''
|
|
172
|
-
res.on('data', (chunk: Buffer) => { body += chunk.toString() })
|
|
173
|
-
res.on('end', () => {
|
|
174
|
-
try {
|
|
175
|
-
const data = JSON.parse(body) as NpmSearchResponse
|
|
176
|
-
resolve(selectNpmPackageForRepo(data.objects ?? [], `${owner}/${name}`))
|
|
177
|
-
} catch {
|
|
178
|
-
resolve(undefined)
|
|
179
|
-
}
|
|
180
|
-
})
|
|
181
|
-
})
|
|
182
|
-
req.on('timeout', () => req.destroy())
|
|
183
|
-
req.on('error', () => resolve(undefined))
|
|
184
|
-
})
|
|
185
|
-
}
|
|
@@ -4,17 +4,12 @@
|
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
6
|
* 分发文件解析:从 package.json 推出装完后必须真实存在的文件 —— 作者声明的入口
|
|
7
|
-
* (exports["."] / main)与 bundle 声明的补丁(dsh.bundle.patch
|
|
8
|
-
* 与 npm 通道(装后 statSync 校验)共用,保证两条通道对同一个包判定一致。
|
|
7
|
+
* (exports["."] / main)与 bundle 声明的补丁(dsh.bundle.patch),供装后校验使用。
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* 例外是补丁自己 mount 了本包:dsh 的《打包与安装插件》里组合包的常规写法就是
|
|
16
|
-
* `- insert: - name: <本包名>`,宿主随后按包名 import 本包,入口那时必须存在。
|
|
17
|
-
* 所以只有补丁真的写了自己的包名,才把这个入口算进分发要求。
|
|
9
|
+
* 只在包自己声明了入口时才要求它存在:宿主加载 bundle 读的是它的 package.json 与声明的补丁,
|
|
10
|
+
* 从不 import 包本身。硬要一个作者没声明过的 index.js,会把完整的包判成分发残缺、把一条假缺陷
|
|
11
|
+
* 推到作者仓库。例外是补丁自己 mount 了本包(组合包的常规写法)—— 那时宿主按包名 import 本包,
|
|
12
|
+
* 入口必须存在,所以只有补丁真的写了自己的包名才把这个入口算进分发要求。
|
|
18
13
|
*/
|
|
19
14
|
|
|
20
15
|
/** exports 条件对象里按优先级尝试的键;Node 语义下 default 最宽,import/require 次之。 */
|
|
@@ -53,7 +48,7 @@ export function resolvePackageEntry(meta: PackageManifest): string | null {
|
|
|
53
48
|
/**
|
|
54
49
|
* 解析 `dsh.bundle.patch` 声明的补丁文件(相对包根,不带 `./` 前缀)。
|
|
55
50
|
* 形状不合规范(既不是字符串也不是字符串数组)在这里不判定:那是 dsh 启动时
|
|
56
|
-
* bundlePatchFiles
|
|
51
|
+
* bundlePatchFiles 的报错职责,装后校验只负责「声明了就要在包里存在」。
|
|
57
52
|
*/
|
|
58
53
|
export function resolveBundlePatchFiles(meta: PackageManifest): string[] {
|
|
59
54
|
const patch = (meta.dsh as { bundle?: { patch?: unknown } } | undefined)?.bundle?.patch
|
|
@@ -69,8 +69,8 @@ export async function registryMissingMarker(
|
|
|
69
69
|
): Promise<string | null> {
|
|
70
70
|
const name = missingPackageOf(output)
|
|
71
71
|
if (name === null) return null
|
|
72
|
-
//
|
|
73
|
-
//
|
|
72
|
+
// 该包是 profile 自己声明的依赖(用户环境里留下的条目)→ 用户环境问题,交给既有的 pnpmMissingDep
|
|
73
|
+
// 提示处理(那条提示就是教用户删掉它),不产出标记、不改归属
|
|
74
74
|
if (profileDependencyNames(opts.profileDir).some((dep) => dep.toLowerCase() === name.toLowerCase())) return null
|
|
75
75
|
const exists = await existsOnOfficialRegistry(name, opts.fetchImpl ?? fetch, opts.timeoutMs ?? 8000)
|
|
76
76
|
if (exists === false) {
|
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
* 预构建 GitHub release
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 校验共用(单一事实来源,避免两端口径漂移)。
|
|
6
|
+
* 预构建 release 直链的仓库身份解析。部分插件(如 loopx)当初以 GitHub release 的 .tgz
|
|
7
|
+
* 安装过,仓库 HEAD 反而是 monorepo 根、走 git 直装会因缺入口文件失败;老安装记录里留的
|
|
8
|
+
* 就是这种 release 直链。这里把「历史安装目标」还原成所属仓库身份,供已装态读回、队列
|
|
9
|
+
* 去重与展示复用(单一事实来源,避免两端口径漂移)。
|
|
11
10
|
*
|
|
12
11
|
* 只接受固定形态的 HTTPS `github.com/<owner>/<repo>/releases/download/<tag>/<asset>.tgz`:
|
|
13
|
-
*
|
|
12
|
+
* 输入是记录/展示数据、绝非 shell 代码,绝不执行其文本;跨仓跳转、外部主机、凭据、
|
|
14
13
|
* 移动的 latest 链接、query/fragment、多余 CLI 参数与 shell 片段一律拒绝。
|
|
15
14
|
*/
|
|
16
15
|
|
|
@@ -22,15 +21,15 @@ export interface GithubReleaseTarget {
|
|
|
22
21
|
repo: string
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
/**
|
|
24
|
+
/** 完整命令前缀:`dsh plugin --profile <p> add|update <target>`(与 profile.ts installTargetOf 同口径)。 */
|
|
26
25
|
const DSH_PLUGIN_CMD_RE = /^dsh[ \t]+plugin[ \t]+--profile(?:[ \t]+|=)[A-Za-z0-9_-]+[ \t]+(?:add|update)[ \t]+(.+)$/i
|
|
27
26
|
|
|
28
27
|
/** 固定形态的 release .tgz 直链:主机、路径段与扩展名全部锁死。 */
|
|
29
28
|
const RELEASE_URL_RE = /^https:\/\/github\.com\/([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)\/releases\/download\/([A-Za-z0-9._+-]+)\/([A-Za-z0-9._+-]+\.tgz)$/
|
|
30
29
|
|
|
31
30
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @param value -
|
|
31
|
+
* 解析一个历史安装目标里的 release 直链。
|
|
32
|
+
* @param value - 完整命令(`dsh plugin --profile web add <url>`)或裸直链。
|
|
34
33
|
* @returns 命中返回 `{ target, repo }`,否则 null。
|
|
35
34
|
*/
|
|
36
35
|
export function githubReleaseTarget(value: string): GithubReleaseTarget | null {
|