dsh-plugin 1.1.1 → 1.2.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 +5 -5
- package/README.md +5 -5
- package/client/client.js +4772 -1659
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +451 -28
- package/lib/index.js +5 -1
- package/lib/services/{install-types.js → install/install-types.js} +8 -2
- package/lib/services/install/install.js +4 -0
- package/lib/services/install/npm-check.js +55 -0
- package/lib/services/{npm-resolve.js → install/npm-resolve.js} +11 -5
- package/lib/services/{preflight.js → install/preflight.js} +23 -7
- package/lib/services/{task-queue.js → install/task-queue.js} +166 -71
- package/lib/services/loader.js +60 -1
- package/lib/services/log.js +138 -0
- package/lib/services/probe.js +157 -0
- package/lib/services/{installed-versions.js → profile/installed-versions.js} +5 -1
- package/lib/services/{pending-restart.js → profile/pending-restart.js} +1 -1
- package/lib/services/{profile.js → profile/profile.js} +59 -4
- package/lib/services/{progress.js → profile/progress.js} +4 -0
- package/lib/services/settings.js +60 -0
- package/lib/types/http/routes.d.ts +1 -1
- package/lib/types/index.d.ts +5 -1
- package/lib/types/services/{install-types.d.ts → install/install-types.d.ts} +13 -4
- package/lib/types/services/{install.d.ts → install/install.d.ts} +9 -5
- package/lib/types/services/install/npm-check.d.ts +12 -0
- package/lib/types/services/{npm-resolve.d.ts → install/npm-resolve.d.ts} +2 -1
- package/lib/types/services/{task-queue.d.ts → install/task-queue.d.ts} +5 -1
- package/lib/types/services/loader.d.ts +14 -4
- package/lib/types/services/log.d.ts +49 -0
- package/lib/types/services/probe.d.ts +30 -0
- package/lib/types/services/{pending-restart.d.ts → profile/pending-restart.d.ts} +9 -3
- package/lib/types/services/{profile.d.ts → profile/profile.d.ts} +6 -2
- package/lib/types/services/{progress.d.ts → profile/progress.d.ts} +4 -0
- package/lib/types/services/settings.d.ts +24 -0
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +346 -121
- package/src/client/components/{CatalogList.tsx → catalog/CatalogList.tsx} +15 -7
- package/src/client/components/{PluginCard.tsx → catalog/PluginCard.tsx} +10 -5
- package/src/client/components/layout/CatalogControls.tsx +80 -0
- package/src/client/components/{CatalogHeader.tsx → layout/CatalogHeader.tsx} +8 -4
- package/src/client/components/{CategoryTabs.tsx → layout/CategoryTabs.tsx} +7 -3
- package/src/client/components/layout/SectionTabs.tsx +60 -0
- package/src/client/components/{AboutModal.tsx → modals/AboutModal.tsx} +11 -7
- package/src/client/components/{HubUpdateModal.tsx → modals/HubUpdateModal.tsx} +9 -5
- package/src/client/components/modals/InstalledDetailModal.tsx +193 -0
- package/src/client/components/modals/LogsModal.tsx +301 -0
- package/src/client/components/modals/LogsPathModal.tsx +161 -0
- package/src/client/components/{NotificationsModal.tsx → modals/NotificationsModal.tsx} +51 -20
- package/src/client/components/{ProgressView.tsx → modals/ProgressView.tsx} +6 -2
- package/src/client/components/{modals.tsx → modals/modals.tsx} +121 -40
- package/src/client/components/{Dropdown.tsx → ui/Dropdown.tsx} +18 -6
- package/src/client/components/ui/Toggle.tsx +28 -0
- package/src/client/components/ui/icons.tsx +343 -0
- package/src/client/components/views/CustomInstallView.tsx +250 -0
- package/src/client/components/views/DiagnosticsView.tsx +172 -0
- package/src/client/components/views/InstalledView.tsx +328 -0
- package/src/client/components/views/LogsView.tsx +150 -0
- package/src/client/components/views/MarketView.tsx +66 -0
- package/src/client/components/views/SettingsView.tsx +273 -0
- package/src/client/data/catalog.ts +38 -0
- package/src/client/data/host.ts +107 -0
- package/src/client/data/hub.ts +41 -0
- package/src/client/hooks/useCatalog.ts +77 -117
- package/src/client/hooks/useIncrementalList.ts +46 -0
- package/src/client/hooks/useLanguage.ts +40 -0
- package/src/client/hooks/useSettings.ts +90 -0
- package/src/client/hooks/useTaskQueue.ts +168 -47
- package/src/client/index.tsx +4 -0
- package/src/client/locales.ts +365 -39
- package/src/client/logic/constants.ts +87 -0
- package/src/client/{lib → logic}/failures.ts +99 -74
- package/src/client/{lib → logic}/format.ts +4 -0
- package/src/client/logic/install-command.ts +56 -0
- package/src/client/logic/installed.ts +207 -0
- package/src/client/logic/normalize.ts +52 -0
- package/src/client/{lib → logic}/renderMarkdown.ts +4 -0
- package/src/client/logic/urls.ts +125 -0
- package/src/client/styles/CustomInstallView.module.css +191 -0
- package/src/client/styles/DiagnosticsView.module.css +231 -0
- package/src/client/styles/Dropdown.module.css +46 -41
- package/src/client/styles/Header.module.css +105 -133
- package/src/client/styles/InstalledView.module.css +447 -0
- package/src/client/styles/List.module.css +11 -1
- package/src/client/styles/LogsView.module.css +503 -0
- package/src/client/styles/Modal.module.css +357 -71
- package/src/client/styles/SectionTabs.module.css +142 -0
- package/src/client/styles/SettingsView.module.css +232 -0
- package/src/client/styles/Toggle.module.css +60 -0
- package/src/client/styles/tokens.module.css +6 -1
- package/src/client/types.ts +28 -4
- package/src/server/http/routes.ts +426 -28
- package/src/server/index.ts +5 -1
- package/src/server/services/{install-types.ts → install/install-types.ts} +13 -5
- package/src/server/services/{install.ts → install/install.ts} +9 -5
- package/src/server/services/install/npm-check.ts +53 -0
- package/src/server/services/{npm-resolve.ts → install/npm-resolve.ts} +11 -5
- package/src/server/services/{preflight.ts → install/preflight.ts} +24 -7
- package/src/server/services/{task-queue.ts → install/task-queue.ts} +168 -68
- package/src/server/services/loader.ts +59 -1
- package/src/server/services/log.ts +167 -0
- package/src/server/services/probe.ts +157 -0
- package/src/server/services/{installed-versions.ts → profile/installed-versions.ts} +5 -1
- package/src/server/services/{pending-restart.ts → profile/pending-restart.ts} +9 -3
- package/src/server/services/{profile.ts → profile/profile.ts} +58 -4
- package/src/server/services/{progress.ts → profile/progress.ts} +4 -0
- package/src/server/services/settings.ts +79 -0
- package/lib/services/install.js +0 -4
- package/src/client/components/CatalogControls.tsx +0 -104
- package/src/client/components/icons.tsx +0 -125
- package/src/client/lib/catalog.ts +0 -272
- package/src/client/lib/env.ts +0 -16
- /package/lib/types/services/{preflight.d.ts → install/preflight.d.ts} +0 -0
- /package/lib/types/services/{installed-versions.d.ts → profile/installed-versions.d.ts} +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
* 系统日志:把关键操作以 JSONL 追加到 `~/.dsh/profiles/<profile>/hub.log`。
|
|
7
|
+
*
|
|
8
|
+
* 每一条日志 = 级别(严重程度)× 类别(功能域):
|
|
9
|
+
* level debug / info / success / warn / error —— 区分报错与正常
|
|
10
|
+
* category install(安装)/ uninstall(卸载)/ update(更新,预留)/
|
|
11
|
+
* diagnostics(诊断)/ settings(设置)/ system(系统)
|
|
12
|
+
* 查看器按级别与类别过滤,就能针对性排查「安装时哪步不兼容」还是
|
|
13
|
+
* 「卸载时哪步不兼容」。读取时超行数上限就地裁剪,防文件无限膨胀;
|
|
14
|
+
* 写入失败绝不影响主流程(日志是辅助设施)。
|
|
15
|
+
*/
|
|
16
|
+
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { loadSettings } from './settings.js';
|
|
20
|
+
/** 日志保留上限:超过后在读取时裁剪为最近 MAX_LOG_LINES 条 */
|
|
21
|
+
export const MAX_LOG_LINES = 2000;
|
|
22
|
+
const CATEGORIES = ['install', 'uninstall', 'update', 'diagnostics', 'settings', 'system'];
|
|
23
|
+
function profileDirectory(profile) {
|
|
24
|
+
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'profiles', profile);
|
|
25
|
+
}
|
|
26
|
+
/** `~` 开头路径展开为用户主目录(Node 不自动展开)。 */
|
|
27
|
+
function expandHome(p) {
|
|
28
|
+
if (p === '~')
|
|
29
|
+
return homedir();
|
|
30
|
+
if (p.startsWith('~/') || p.startsWith('~\\'))
|
|
31
|
+
return join(homedir(), p.slice(2));
|
|
32
|
+
return p;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 把用户填写的日志位置覆盖解析成实际文件路径:
|
|
36
|
+
* 以 .log 结尾视为文件本身,否则视为目录并在其中写 hub.log;相对路径按当前目录归一化。
|
|
37
|
+
*/
|
|
38
|
+
export function customLogFile(override) {
|
|
39
|
+
const trimmed = override.trim();
|
|
40
|
+
const expanded = expandHome(trimmed);
|
|
41
|
+
const file = /\.log$/i.test(expanded) ? expanded : join(expanded, 'hub.log');
|
|
42
|
+
return resolve(file);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 真正默认的日志文件路径:`~/.dsh/profiles/<profile>/hub.log`(不受设置覆盖影响)。
|
|
46
|
+
* 弹窗「恢复默认」、目录选择器起点等需要「未自定义时的位置」都从这里取 ——
|
|
47
|
+
* 不同机器/平台(用户主目录不同)这个值都不同,必须动态计算而非写死。
|
|
48
|
+
*/
|
|
49
|
+
export function defaultLogFilePath(profile) {
|
|
50
|
+
return join(profileDirectory(profile), 'hub.log');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 当前生效的日志文件路径:设置里填了 logPath 用自定义位置(目录或文件),
|
|
54
|
+
* 否则默认 `~/.dsh/profiles/<profile>/hub.log`。默认位置永远合法可用,无需用户干预。
|
|
55
|
+
*/
|
|
56
|
+
export function logFilePath(profile) {
|
|
57
|
+
const custom = loadSettings(profile).logPath?.trim() ?? '';
|
|
58
|
+
return custom === '' ? defaultLogFilePath(profile) : customLogFile(custom);
|
|
59
|
+
}
|
|
60
|
+
/** 追加一条系统日志(JSONL 一行一条)。 */
|
|
61
|
+
export function appendLog(profile, entry) {
|
|
62
|
+
try {
|
|
63
|
+
const file = logFilePath(profile);
|
|
64
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
65
|
+
appendFileSync(file, `${JSON.stringify(entry)}\n`);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// 日志失败不阻断主流程
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** 兼容旧格式:早期无 category(level 用 info/ok/fail/warn),归一化到新版。 */
|
|
72
|
+
function normalize(raw) {
|
|
73
|
+
if (typeof raw !== 'object' || raw === null)
|
|
74
|
+
return null;
|
|
75
|
+
const e = raw;
|
|
76
|
+
if (typeof e.at !== 'number')
|
|
77
|
+
return null;
|
|
78
|
+
let level = 'info';
|
|
79
|
+
if (e.level === 'ok' || e.level === 'success')
|
|
80
|
+
level = 'success';
|
|
81
|
+
else if (e.level === 'fail' || e.level === 'error')
|
|
82
|
+
level = 'error';
|
|
83
|
+
else if (e.level === 'warn')
|
|
84
|
+
level = 'warn';
|
|
85
|
+
else if (e.level === 'debug')
|
|
86
|
+
level = 'debug';
|
|
87
|
+
const category = CATEGORIES.includes(e.category) ? e.category : 'system';
|
|
88
|
+
return {
|
|
89
|
+
at: e.at,
|
|
90
|
+
level,
|
|
91
|
+
category,
|
|
92
|
+
event: typeof e.event === 'string' ? e.event : '',
|
|
93
|
+
message: typeof e.message === 'string' ? e.message : '',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** 读取系统日志(按时间正序,最新在末尾),超上限就地裁剪;支持过滤与分页。 */
|
|
97
|
+
export function readLog(profile, opts = {}) {
|
|
98
|
+
const { offset = 0, limit = 200 } = opts;
|
|
99
|
+
try {
|
|
100
|
+
const file = logFilePath(profile);
|
|
101
|
+
const raw = readFileSync(file, 'utf8');
|
|
102
|
+
let lines = raw.split('\n').filter((l) => l.trim() !== '');
|
|
103
|
+
if (lines.length > MAX_LOG_LINES) {
|
|
104
|
+
lines = lines.slice(-MAX_LOG_LINES);
|
|
105
|
+
writeFileSync(file, `${lines.join('\n')}\n`);
|
|
106
|
+
}
|
|
107
|
+
// 正序解析后倒序(最新在前)再过滤分页
|
|
108
|
+
let entries = [];
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
let parsed = null;
|
|
111
|
+
try {
|
|
112
|
+
parsed = JSON.parse(line);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const entry = normalize(parsed);
|
|
118
|
+
if (entry !== null)
|
|
119
|
+
entries.push(entry);
|
|
120
|
+
}
|
|
121
|
+
entries.reverse();
|
|
122
|
+
const needle = opts.query?.trim().toLowerCase() ?? '';
|
|
123
|
+
if (opts.category !== undefined && opts.category !== 'all') {
|
|
124
|
+
entries = entries.filter((e) => e.category === opts.category);
|
|
125
|
+
}
|
|
126
|
+
if (opts.level !== undefined && opts.level !== 'all') {
|
|
127
|
+
entries = entries.filter((e) => e.level === opts.level);
|
|
128
|
+
}
|
|
129
|
+
if (needle !== '') {
|
|
130
|
+
entries = entries.filter((e) => e.event.toLowerCase().includes(needle) || e.message.toLowerCase().includes(needle));
|
|
131
|
+
}
|
|
132
|
+
const total = entries.length;
|
|
133
|
+
return { entries: entries.slice(offset, offset + limit), total };
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { entries: [], total: 0 };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
* 连通性探测:对单个 HTTPS 目标发 GET,测速并返回 HTTP 状态码。
|
|
7
|
+
*
|
|
8
|
+
* 实现方式:spawn 系统 curl 子进程并注入代理环境变量(HTTP_PROXY/HTTPS_PROXY),
|
|
9
|
+
* 与安装通道(pnpm/git 子进程)使用同一套代理环境。
|
|
10
|
+
*
|
|
11
|
+
* 代理来源由调用方决定(routes.ts):设置里的代理 → 系统代理 → 环境变量 → 直连。
|
|
12
|
+
* systemProxy() 读取操作系统级代理(macOS scutil / Windows 注册表),
|
|
13
|
+
* 使系统代理对 Node 进程可见。
|
|
14
|
+
*/
|
|
15
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
16
|
+
const probeFail = () => ({ ok: false, ms: null, status: null });
|
|
17
|
+
/** macOS 系统代理:scutil --proxy 输出里的 HTTP(S) 代理;未开启返回 null。 */
|
|
18
|
+
function macSystemProxy() {
|
|
19
|
+
try {
|
|
20
|
+
const out = spawnSync('scutil', ['--proxy'], { encoding: 'utf8', timeout: 1500 });
|
|
21
|
+
if (out.status !== 0)
|
|
22
|
+
return null;
|
|
23
|
+
const txt = out.stdout ?? '';
|
|
24
|
+
const val = (key) => {
|
|
25
|
+
const m = new RegExp(`^\\s*${key}\\s*:\\s*(.+)$`, 'm').exec(txt);
|
|
26
|
+
const v = m?.[1]?.trim();
|
|
27
|
+
return v !== undefined && v !== '' ? v : undefined;
|
|
28
|
+
};
|
|
29
|
+
const enabled = (val('HTTPSEnable') ?? val('HTTPEnable')) === '1';
|
|
30
|
+
const host = val('HTTPSProxy') ?? val('HTTPProxy');
|
|
31
|
+
if (!enabled || host === undefined)
|
|
32
|
+
return null;
|
|
33
|
+
const port = val('HTTPSPort') ?? val('HTTPPort') ?? '80';
|
|
34
|
+
if (!/^\d+$/.test(port))
|
|
35
|
+
return null;
|
|
36
|
+
return `http://${host}:${port}`;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Windows 系统代理:WinINET Internet 设置注册表;未开启返回 null。 */
|
|
43
|
+
function winSystemProxy() {
|
|
44
|
+
try {
|
|
45
|
+
const key = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings';
|
|
46
|
+
const enable = spawnSync('reg', ['query', key, '/v', 'ProxyEnable'], { encoding: 'utf8', timeout: 1500 });
|
|
47
|
+
const server = spawnSync('reg', ['query', key, '/v', 'ProxyServer'], { encoding: 'utf8', timeout: 1500 });
|
|
48
|
+
if (!/0x1/i.test(enable.stdout ?? ''))
|
|
49
|
+
return null;
|
|
50
|
+
const m = /([^:\s=]+):(\d+)/.exec(server.stdout ?? '');
|
|
51
|
+
if (m === null)
|
|
52
|
+
return null;
|
|
53
|
+
return `http://${m[1]}:${m[2]}`;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 操作系统级代理(macOS 系统网络设置 / Windows Internet 设置)。
|
|
61
|
+
* Node 内置 http(s) 不读系统代理,浏览器挂的代理 Node 看不见 —— 这里读出来
|
|
62
|
+
* 作为默认代理,保证「浏览器能开、安装/诊断就能通」。
|
|
63
|
+
* Linux 没有统一的系统代理入口,返回 null(交给环境变量 / 设置里的代理)。
|
|
64
|
+
*/
|
|
65
|
+
export function systemProxy() {
|
|
66
|
+
if (process.platform === 'darwin')
|
|
67
|
+
return macSystemProxy();
|
|
68
|
+
if (process.platform === 'win32')
|
|
69
|
+
return winSystemProxy();
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* curl 子进程探测:spawn curl -s -w '%{http_code}' 并注入代理 env。
|
|
74
|
+
* curl 读 HTTP_PROXY/HTTPS_PROXY 是原生行为,与 pnpm/git 安装通道同一套代理口径;
|
|
75
|
+
* 失败(curl 不存在 / 连接失败 / 超时)都归为不可达,全程不抛错。
|
|
76
|
+
*/
|
|
77
|
+
function curlProbe(url, proxy, timeoutMs) {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
const started = Date.now();
|
|
80
|
+
const env = { ...process.env };
|
|
81
|
+
if (proxy !== '') {
|
|
82
|
+
env.HTTP_PROXY = proxy;
|
|
83
|
+
env.HTTPS_PROXY = proxy;
|
|
84
|
+
env.http_proxy = proxy;
|
|
85
|
+
env.https_proxy = proxy;
|
|
86
|
+
}
|
|
87
|
+
const discard = process.platform === 'win32' ? 'NUL' : '/dev/null';
|
|
88
|
+
const child = spawn('curl', [
|
|
89
|
+
'-s', '-o', discard, '-w', '%{http_code}',
|
|
90
|
+
'--max-time', String(Math.max(1, Math.round(timeoutMs / 1000))),
|
|
91
|
+
url,
|
|
92
|
+
], { env });
|
|
93
|
+
let code = '';
|
|
94
|
+
child.stdout.on('data', (c) => { code += c.toString(); });
|
|
95
|
+
let done = false;
|
|
96
|
+
const finish = (r) => {
|
|
97
|
+
if (done)
|
|
98
|
+
return;
|
|
99
|
+
done = true;
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
resolve(r);
|
|
102
|
+
};
|
|
103
|
+
child.on('error', () => finish(probeFail()));
|
|
104
|
+
child.on('close', () => {
|
|
105
|
+
const status = /^\d+$/.test(code.trim()) ? Number(code.trim()) : null;
|
|
106
|
+
finish({ ok: status !== null && status >= 100 && status < 400, ms: Date.now() - started, status });
|
|
107
|
+
});
|
|
108
|
+
// 超时兜底:curl --max-time 到点自行退出,此处再设一个进程级超时防止悬挂
|
|
109
|
+
const timer = setTimeout(() => { child.kill(); finish(probeFail()); }, timeoutMs + 1000);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 探测一个 HTTPS 目标:proxy 非空注入给 curl 子进程走代理,否则直连。
|
|
114
|
+
* 供系统诊断 /diagnostics 使用;目标 URL 非法返回不可达。
|
|
115
|
+
*/
|
|
116
|
+
export function probeUrl(url, proxy, timeoutMs) {
|
|
117
|
+
let target;
|
|
118
|
+
try {
|
|
119
|
+
target = new URL(url);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return Promise.resolve(probeFail());
|
|
123
|
+
}
|
|
124
|
+
return curlProbe(target.href, proxy, timeoutMs);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* curl 子进程抓取响应体(与 probeUrl 同一套代理 env 注入)。
|
|
128
|
+
* 供服务端 /catalog 代理路由使用:目录/统计数据经此拉到服务端再转给浏览器,
|
|
129
|
+
* 使「目录数据请求走设置里的代理」与 npm / git 安装通道口径一致。
|
|
130
|
+
* 失败(curl 不存在 / 连接失败 / 超时 / 非零退出)时 ok=false,全程不抛错。
|
|
131
|
+
*/
|
|
132
|
+
export function fetchViaCurl(url, proxy, timeoutMs) {
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
const env = { ...process.env };
|
|
135
|
+
if (proxy !== '') {
|
|
136
|
+
env.HTTP_PROXY = proxy;
|
|
137
|
+
env.HTTPS_PROXY = proxy;
|
|
138
|
+
env.http_proxy = proxy;
|
|
139
|
+
env.https_proxy = proxy;
|
|
140
|
+
}
|
|
141
|
+
const child = spawn('curl', ['-s', '--max-time', String(Math.max(1, Math.round(timeoutMs / 1000))), url], { env });
|
|
142
|
+
let body = '';
|
|
143
|
+
child.stdout.on('data', (c) => { body += c.toString(); });
|
|
144
|
+
let done = false;
|
|
145
|
+
const finish = (ok) => {
|
|
146
|
+
if (done)
|
|
147
|
+
return;
|
|
148
|
+
done = true;
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
resolve({ ok, body });
|
|
151
|
+
};
|
|
152
|
+
child.on('error', () => finish(false));
|
|
153
|
+
child.on('close', (code) => finish(code === 0));
|
|
154
|
+
// 超时兜底:curl --max-time 到点自行退出,此处再设一个进程级超时防止悬挂
|
|
155
|
+
const timer = setTimeout(() => { child.kill(); finish(false); }, timeoutMs + 1000);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
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
|
+
*
|
|
2
6
|
* Installed-plugin version tracking.
|
|
3
7
|
*
|
|
4
8
|
* The installed table (`readInstalled` in routes.ts) only knows the manifest
|
|
5
|
-
* spec of each dependency
|
|
9
|
+
* spec of each Git dependency, which carries no version.
|
|
6
10
|
* To let the Plugin Hub tell "an update is available", we record the catalog
|
|
7
11
|
* signal present at install time into a small per-profile JSON file, then
|
|
8
12
|
* compare it against the catalog's current signal later.
|
|
@@ -2,7 +2,7 @@ const pendingRestarts = [];
|
|
|
2
2
|
/**
|
|
3
3
|
* 登记一条待重启记录(同一目标去重)。
|
|
4
4
|
* - 卸载:目标刚装完未重启(已有待重启记录)→ 宿主从未加载过它,卸完磁盘已干净、
|
|
5
|
-
* loader
|
|
5
|
+
* loader 无残留,无需再提醒重启,直接移除该条;否则(已被宿主加载过)登记「卸载后待重启清理」。
|
|
6
6
|
* - 安装:已有记录则刷新为安装语义(如卸载后未重启又重装),否则新登记。
|
|
7
7
|
*/
|
|
8
8
|
export function addPendingRestart(target, kind) {
|
|
@@ -1,11 +1,15 @@
|
|
|
1
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
|
+
*
|
|
2
6
|
* 宿主 profile 与目标校验工具:解析当前 profile、目录定位、安装目标语法校验,
|
|
3
7
|
* 以及 pnpm allowBuilds 白名单的写入(失败恢复路径)。
|
|
4
8
|
*/
|
|
5
9
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
6
10
|
import { homedir } from 'node:os';
|
|
7
11
|
import { join } from 'node:path';
|
|
8
|
-
import { PACKAGE_RE, REPO_RE } from '
|
|
12
|
+
import { PACKAGE_RE, REPO_RE } from '../install/install-types.js';
|
|
9
13
|
/** Resolve the active profile from the booted CLI args, falling back to `web`. */
|
|
10
14
|
export function readProfileArg(fallback = 'web') {
|
|
11
15
|
const index = process.argv.indexOf('--profile');
|
|
@@ -16,11 +20,62 @@ export function readProfileArg(fallback = 'web') {
|
|
|
16
20
|
export function profileDirectory(profile) {
|
|
17
21
|
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'profiles', profile);
|
|
18
22
|
}
|
|
19
|
-
/** Build a safe
|
|
23
|
+
/** Build a safe explicit HTTPS Git target, or null when the repo is unsafe. */
|
|
20
24
|
export function githubTarget(repo) {
|
|
21
25
|
if (typeof repo !== 'string' || !REPO_RE.test(repo))
|
|
22
26
|
return null;
|
|
23
|
-
return `github
|
|
27
|
+
return `git+https://github.com/${repo}.git`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* DSH 命令行前缀(`dsh plugin [--profile|-p <p>] add <target>`)—— 提取其中的安装目标;
|
|
31
|
+
* 非命令输入原样返回。兼容「直接粘贴完整命令」(防用户输错):--profile 段可选,
|
|
32
|
+
* 支持 `-p` 简写与 `--profile=web` 等号形式,命令大小写不敏感。
|
|
33
|
+
*/
|
|
34
|
+
const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+(?:(?:--profile|-p)(?:=|\s+)\S+\s*)?add\s+(.+)$/i;
|
|
35
|
+
export function installTargetOf(value) {
|
|
36
|
+
const input = typeof value === 'string' ? value.trim() : '';
|
|
37
|
+
const match = DSH_PLUGIN_CMD_RE.exec(input);
|
|
38
|
+
return match !== null ? match[1].trim() : input;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* npm 全局安装命令(官方 README 的 `npm install -g <pkgs>`,如 dsh-tui 的安装命令)——
|
|
42
|
+
* 提取 -g 之后的包列表。支持 `npm install|i` 与 `-g|--global`;任一包名非法(或格式
|
|
43
|
+
* 不匹配)返回 null,由调用方按「不支持的目标」处理。与客户端 CustomInstallView 同口径。
|
|
44
|
+
*/
|
|
45
|
+
const NPM_GLOBAL_CMD_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(.+)$/i;
|
|
46
|
+
export function globalNpmPackagesOf(value) {
|
|
47
|
+
const input = typeof value === 'string' ? value.trim() : '';
|
|
48
|
+
const match = NPM_GLOBAL_CMD_RE.exec(input);
|
|
49
|
+
if (match === null)
|
|
50
|
+
return null;
|
|
51
|
+
const packages = match[1].trim().split(/\s+/).filter((p) => p !== '');
|
|
52
|
+
// 包名逐个校验 + 拒绝以 - 开头的 token(npm 会把 --xxx 当参数而非包名,防注入任意 CLI 参数)
|
|
53
|
+
if (packages.length === 0 || packages.some((p) => !validPackageName(p) || p.startsWith('-')))
|
|
54
|
+
return null;
|
|
55
|
+
return packages;
|
|
56
|
+
}
|
|
57
|
+
/** Extract an owner/repo identity from a catalog value or an installed Git spec. */
|
|
58
|
+
export function githubRepoOf(value) {
|
|
59
|
+
if (typeof value !== 'string')
|
|
60
|
+
return null;
|
|
61
|
+
const input = value.trim();
|
|
62
|
+
if (REPO_RE.test(input))
|
|
63
|
+
return input;
|
|
64
|
+
const patterns = [
|
|
65
|
+
/^github:([^/]+)\/([^/]+)$/i,
|
|
66
|
+
/^(?:git\+)?https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:[?#].*)?$/i,
|
|
67
|
+
/^(?:git\+)?ssh:\/\/(?:git@)?github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:[?#].*)?$/i,
|
|
68
|
+
/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?(?:[?#].*)?$/i,
|
|
69
|
+
];
|
|
70
|
+
for (const pattern of patterns) {
|
|
71
|
+
const match = pattern.exec(input);
|
|
72
|
+
if (match === null)
|
|
73
|
+
continue;
|
|
74
|
+
const repo = `${match[1]}/${match[2]}`;
|
|
75
|
+
if (REPO_RE.test(repo))
|
|
76
|
+
return repo;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
24
79
|
}
|
|
25
80
|
/** Validate an npm package name (uninstall target grammar). */
|
|
26
81
|
export function validPackageName(name) {
|
|
@@ -28,7 +83,7 @@ export function validPackageName(name) {
|
|
|
28
83
|
}
|
|
29
84
|
/**
|
|
30
85
|
* The exact `allowBuilds` key pnpm printed in its
|
|
31
|
-
* `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` hint. For
|
|
86
|
+
* `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` hint. For Git installs pnpm
|
|
32
87
|
* matches against `name@<resolved fetch URL>` — the URL it actually fetches,
|
|
33
88
|
* e.g. `https://codeload.github.com/<owner>/<repo>/tar.gz/<commit>` — not the
|
|
34
89
|
* `git+ssh://`/branch spec. A bare name or a `git+` spec does not match, so
|
|
@@ -1,4 +1,8 @@
|
|
|
1
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
|
+
*
|
|
2
6
|
* Pure progress-estimation helpers shared by the install runner.
|
|
3
7
|
*
|
|
4
8
|
* The dsh CLI wraps pnpm, which refreshes its `Progress: …` line in place
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
* Hub settings persistence: one `hub-settings.json` per active profile
|
|
7
|
+
* (next to pnpm-workspace.yaml). All fields optional; missing keys fall
|
|
8
|
+
* back to DEFAULT_SETTINGS so old files stay readable as new options land.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
11
|
+
import { dirname, join } from 'node:path';
|
|
12
|
+
import { profileDirectory } from './profile/profile.js';
|
|
13
|
+
export const DEFAULT_SETTINGS = {
|
|
14
|
+
checkUpdatesOnStart: true,
|
|
15
|
+
npmRegistry: '',
|
|
16
|
+
proxy: '',
|
|
17
|
+
enableNpmInstall: true,
|
|
18
|
+
enableGitInstall: true,
|
|
19
|
+
enableDshInstall: true,
|
|
20
|
+
logPath: '',
|
|
21
|
+
};
|
|
22
|
+
export function settingsFile(profile) {
|
|
23
|
+
return join(profileDirectory(profile), 'hub-settings.json');
|
|
24
|
+
}
|
|
25
|
+
/** 读取当前 profile 的设置;文件缺失/损坏时回退默认值(只读路径绝不抛错)。 */
|
|
26
|
+
export function loadSettings(profile) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = JSON.parse(readFileSync(settingsFile(profile), 'utf8'));
|
|
29
|
+
return {
|
|
30
|
+
...DEFAULT_SETTINGS,
|
|
31
|
+
...(typeof raw === 'object' && raw !== null ? raw : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { ...DEFAULT_SETTINGS };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** 合并并持久化设置(部分更新),返回合并后的完整设置。 */
|
|
39
|
+
export function saveSettings(profile, patch) {
|
|
40
|
+
const next = { ...loadSettings(profile), ...patch };
|
|
41
|
+
const file = settingsFile(profile);
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
44
|
+
writeFileSync(file, `${JSON.stringify(next, null, 2)}\n`);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// 持久化失败不阻断本次会话:内存值仍生效,下次写入再试
|
|
48
|
+
}
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
/** 重置为默认设置:删除设置文件,下次读取全部回退默认值(陈旧/白名单外键一并清除)。 */
|
|
52
|
+
export function resetSettings(profile) {
|
|
53
|
+
try {
|
|
54
|
+
rmSync(settingsFile(profile), { force: true });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// 删除失败不阻断:loadSettings 仍回退默认值
|
|
58
|
+
}
|
|
59
|
+
return { ...DEFAULT_SETTINGS };
|
|
60
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
-
import { readProfileArg, type LoaderHandle } from '../services/install.ts';
|
|
2
|
+
import { readProfileArg, type LoaderHandle } from '../services/install/install.ts';
|
|
3
3
|
export interface WebRoute {
|
|
4
4
|
kind: 'exact';
|
|
5
5
|
path: string;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
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
|
+
*
|
|
2
6
|
* dsh-plugin host entry: mounts the Plugin Hub HTTP routes once the host
|
|
3
7
|
* web server service is available. The browser bundle (src/client) talks to
|
|
4
8
|
* these same-origin routes to perform real installs from inside the app.
|
|
5
9
|
*/
|
|
6
|
-
import { type LoaderHandle } from './services/install.ts';
|
|
10
|
+
import { type LoaderHandle } from './services/install/install.ts';
|
|
7
11
|
import { type WebServerService } from './http/routes.ts';
|
|
8
12
|
export declare const name = "dsh-plugin";
|
|
9
13
|
export interface Config {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
/**
|
|
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
|
+
* 安装/卸载运行时的公共类型与常量(无依赖,供各服务模块复用)。
|
|
7
|
+
*/
|
|
2
8
|
/** 一个 pnpm 安装子进程的结果快照。 */
|
|
3
9
|
export interface InstallResult {
|
|
4
10
|
exitCode: number | null;
|
|
@@ -9,7 +15,7 @@ export interface InstallResult {
|
|
|
9
15
|
}
|
|
10
16
|
export interface InstallTask {
|
|
11
17
|
id: number;
|
|
12
|
-
/**
|
|
18
|
+
/** 操作目标:显式 HTTPS Git URL(安装)或 npm 包名(卸载),冲突/恢复时需要展示给用户 */
|
|
13
19
|
target: string;
|
|
14
20
|
/** 展示用目标(owner/repo):npm 安装时 target 是包名,前端恢复用此字段显示仓库名,保持用户无感知 */
|
|
15
21
|
displayTarget?: string;
|
|
@@ -17,6 +23,8 @@ export interface InstallTask {
|
|
|
17
23
|
* 失败提 Issue 时如实贴给作者,作者据此反推正确的 npm 包名(组织 scope 与 GitHub 用户名不一致时
|
|
18
24
|
* 仅凭仓库名猜不到,作者看到我们查过/试过的命令就能直接指认)。 */
|
|
19
25
|
attempts: string[];
|
|
26
|
+
/** 全局 npm 安装(官方 `npm install -g <pkgs>`):非空时任务执行全局 npm 安装,不进任何 profile */
|
|
27
|
+
globalNpm?: string[];
|
|
20
28
|
action: 'add' | 'remove';
|
|
21
29
|
/** 队列语义:pending 排队中 / running 执行中 / done / failed / cancelled(用户取消) */
|
|
22
30
|
status: 'pending' | 'running' | 'done' | 'failed' | 'cancelled';
|
|
@@ -45,6 +53,7 @@ export interface QueueItem {
|
|
|
45
53
|
target: string;
|
|
46
54
|
timeoutMs?: number;
|
|
47
55
|
env?: NodeJS.ProcessEnv;
|
|
56
|
+
globalNpm?: string[];
|
|
48
57
|
};
|
|
49
58
|
}
|
|
50
59
|
/**
|
|
@@ -52,14 +61,14 @@ export interface QueueItem {
|
|
|
52
61
|
* 它自然清空,正好等于「重启后提醒消失」的语义,无需任何重启探测。
|
|
53
62
|
*/
|
|
54
63
|
export interface PendingRestart {
|
|
55
|
-
/**
|
|
64
|
+
/** Git 安装目标(与任务一致;卸载时为展示用的 owner/repo) */
|
|
56
65
|
target: string;
|
|
57
66
|
/** 待重启语义:install 装完等挂载生效 / uninstall 卸完等清理 loader 残留 */
|
|
58
67
|
kind: 'install' | 'uninstall';
|
|
59
68
|
/** 任务完成时刻(epoch ms) */
|
|
60
69
|
at: number;
|
|
61
70
|
}
|
|
62
|
-
/** Grammar of
|
|
71
|
+
/** Grammar of a catalog GitHub repository identity (`owner/repo`). */
|
|
63
72
|
export declare const REPO_RE: RegExp;
|
|
64
73
|
/** Grammar of an npm package name (used for uninstall targets). */
|
|
65
74
|
export declare const PACKAGE_RE: RegExp;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
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
|
+
*
|
|
2
6
|
* Install runner 的统一出口:执行真实插件变更(spawn 官方 dsh CLI 子进程)。
|
|
3
7
|
* 实现按职责拆分在同目录子模块,本文件只做公共 API 的 re-export,保持外部
|
|
4
|
-
* import 面稳定(routes.ts / index.ts 仍从 `services/install.ts` 引入)。
|
|
8
|
+
* import 面稳定(routes.ts / index.ts 仍从 `services/install/install.ts` 引入)。
|
|
5
9
|
*
|
|
6
10
|
* 模块划分:
|
|
7
11
|
* - install-types.ts 公共类型与常量
|
|
@@ -11,8 +15,8 @@
|
|
|
11
15
|
* - task-queue.ts 任务注册表 + FIFO 队列 worker + 子进程生命周期
|
|
12
16
|
*/
|
|
13
17
|
export type { InstallResult, InstallTask, Invocation, PendingRestart, QueueItem } from './install-types.ts';
|
|
14
|
-
export type { LoaderHandle } from '
|
|
15
|
-
export { addPendingRestart, clearPendingRestart, listPendingRestarts } from '
|
|
16
|
-
export { dumpLoaderEntries, removeLoadedEntry } from '
|
|
17
|
-
export { addAllowBuildsKey, githubTarget, parseAllowBuildsKey, readProfileArg, validPackageName } from '
|
|
18
|
+
export type { LoaderHandle } from '../loader.ts';
|
|
19
|
+
export { addPendingRestart, clearPendingRestart, listPendingRestarts } from '../profile/pending-restart.ts';
|
|
20
|
+
export { dumpLoaderEntries, removeLoadedEntry } from '../loader.ts';
|
|
21
|
+
export { addAllowBuildsKey, githubRepoOf, githubTarget, globalNpmPackagesOf, installTargetOf, parseAllowBuildsKey, readProfileArg, validPackageName } from '../profile/profile.ts';
|
|
18
22
|
export { activeTask, cancelTask, getTask, hasQueuedTarget, hasRunningTask, runPluginMutation, startPluginMutation } from './task-queue.ts';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** npm arborist 内部崩溃特征:`Cannot read properties of null (reading 'edgesOut')`。 */
|
|
2
|
+
export declare const NPM_CRASH_EDGES_OUT_RE: RegExp;
|
|
3
|
+
/** 该缺陷在 npm 11.6.0 起不再复现;低于此版本遇到 edgesOut 崩溃即判「本机 npm 版本过低」。 */
|
|
4
|
+
export declare const NPM_MIN_VERSION: number[];
|
|
5
|
+
/** 读本机 npm 版本(major.minor.patch);npm 不在 PATH 或执行失败/超时时返回 null(不妄下结论)。 */
|
|
6
|
+
export declare function npmVersionOf(env: NodeJS.ProcessEnv | undefined): [number, number, number] | null;
|
|
7
|
+
/**
|
|
8
|
+
* 安装失败输出含 npm arborist edgesOut 崩溃特征,且本机 npm 版本低于阈值时,返回
|
|
9
|
+
* `[npm-too-low] npm@X.Y.Z …` 标记行;否则返回 null。前端据此展示「本机 npm 版本过低」
|
|
10
|
+
* 的准确原因,避免该报错被误判成插件打包/分发问题。
|
|
11
|
+
*/
|
|
12
|
+
export declare function npmTooLowMarker(output: string, env?: NodeJS.ProcessEnv): string | null;
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
* 反查 repo(`owner/repo`)对应的官方 npm 包名;未命中、网络异常或超时返回 null。
|
|
3
3
|
* 返回 null 不代表仓库一定没有 npm 包,只代表本次未能确认 —— 调用方应保留
|
|
4
4
|
* 原有错误路径,反查只是额外的一次尝试。
|
|
5
|
+
* @param registry - npm 镜像源地址(空串 = 官方 https://registry.npmjs.org)
|
|
5
6
|
*/
|
|
6
|
-
export declare function resolveNpmPackage(repo: string): Promise<string | null>;
|
|
7
|
+
export declare function resolveNpmPackage(repo: string, registry?: string): Promise<string | null>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InstallResult, InstallTask } from './install-types.ts';
|
|
2
|
-
import type { LoaderHandle } from '
|
|
2
|
+
import type { LoaderHandle } from '../loader.ts';
|
|
3
3
|
/** Snapshot of a task (keeps the live object untouched by consumers). */
|
|
4
4
|
export declare function getTask(id: number): InstallTask | undefined;
|
|
5
5
|
export interface ActiveTaskInfo {
|
|
@@ -42,6 +42,8 @@ export declare function startPluginMutation(options: {
|
|
|
42
42
|
target: string;
|
|
43
43
|
timeoutMs?: number;
|
|
44
44
|
env?: NodeJS.ProcessEnv;
|
|
45
|
+
/** 全局 npm 安装(官方 `npm install -g <pkgs>`):非空时执行全局安装,不进任何 profile、无需宿主重启 */
|
|
46
|
+
globalNpm?: string[];
|
|
45
47
|
/** 待重启行的展示目标(owner/repo):卸载时用于把 npm 包名映射回仓库名 */
|
|
46
48
|
displayTarget?: string;
|
|
47
49
|
/** 运行中 loader:卸载成功后主动移除条目、立即生效(缺失时卸载仍需重启清理) */
|
|
@@ -79,4 +81,6 @@ export declare function runPluginMutation(options: {
|
|
|
79
81
|
displayTarget?: string;
|
|
80
82
|
/** 运行中 loader:卸载成功后主动移除条目、立即生效(缺失时卸载仍需重启清理) */
|
|
81
83
|
uninstallLoader?: LoaderHandle;
|
|
84
|
+
/** 全局 npm 安装(官方 `npm install -g <pkgs>`):非空时执行全局安装,不进任何 profile、无需宿主重启 */
|
|
85
|
+
globalNpm?: string[];
|
|
82
86
|
}): Promise<InstallResult>;
|