dsh-plugin 1.3.4 → 1.3.5
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 +8 -0
- package/README.md +8 -0
- package/client/client.js +333 -310
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +3 -1
- package/lib/services/install/task-queue.js +29 -15
- package/lib/services/profile/profile.js +4 -3
- package/lib/types/services/install/install-types.d.ts +2 -2
- package/lib/types/services/install/task-queue.d.ts +3 -3
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +2 -0
- package/src/client/components/views/CustomInstallView.tsx +35 -14
- package/src/client/locales.ts +8 -6
- package/src/client/logic/renderMarkdown.ts +9 -5
- package/src/client/styles/CustomInstallView.module.css +25 -0
- package/src/server/http/routes.ts +3 -1
- package/src/server/services/install/install-types.ts +2 -2
- package/src/server/services/install/task-queue.ts +36 -20
- package/src/server/services/profile/profile.ts +4 -3
|
@@ -38,7 +38,7 @@ export function getTask(id: number): InstallTask | undefined {
|
|
|
38
38
|
export interface ActiveTaskInfo {
|
|
39
39
|
id: number
|
|
40
40
|
target: string
|
|
41
|
-
action: 'add' | 'remove'
|
|
41
|
+
action: 'add' | 'remove' | 'update'
|
|
42
42
|
status: 'pending' | 'running'
|
|
43
43
|
progress: number
|
|
44
44
|
lines: string[]
|
|
@@ -158,13 +158,23 @@ function stopChild(child: ReturnType<typeof spawn>): void {
|
|
|
158
158
|
child.kill('SIGKILL')
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/** 系统日志类别:安装/卸载/更新三分,针对性地判断「哪一步不兼容」。 */
|
|
162
|
+
function mutationCategory(action: 'add' | 'remove' | 'update'): 'install' | 'uninstall' | 'update' {
|
|
163
|
+
return action === 'remove' ? 'uninstall' : action === 'update' ? 'update' : 'install'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** 系统日志动作词:开始安装/开始卸载/开始更新。 */
|
|
167
|
+
function mutationVerb(action: 'add' | 'remove' | 'update'): string {
|
|
168
|
+
return action === 'remove' ? '开始卸载' : action === 'update' ? '开始更新' : '开始安装'
|
|
169
|
+
}
|
|
170
|
+
|
|
161
171
|
/**
|
|
162
172
|
* Enqueue a plugin mutation and return its task. Tasks run strictly serially:
|
|
163
173
|
* the queue worker starts the next one only after the previous finishes.
|
|
164
174
|
* Progress is visible through `getTask(id)` / `activeTask()` until done.
|
|
165
175
|
*/
|
|
166
176
|
export function startPluginMutation(options: {
|
|
167
|
-
action: 'add' | 'remove'
|
|
177
|
+
action: 'add' | 'remove' | 'update'
|
|
168
178
|
profile: string
|
|
169
179
|
target: string
|
|
170
180
|
timeoutMs?: number
|
|
@@ -193,14 +203,14 @@ export function startPluginMutation(options: {
|
|
|
193
203
|
})
|
|
194
204
|
}
|
|
195
205
|
queue.push({ task, options: { ...options } })
|
|
196
|
-
//
|
|
197
|
-
const category = options.action
|
|
206
|
+
// 系统日志:记录任务入队(安装/卸载/更新开始)
|
|
207
|
+
const category = mutationCategory(options.action)
|
|
198
208
|
appendLog(options.profile, {
|
|
199
209
|
at: Date.now(),
|
|
200
210
|
level: 'info',
|
|
201
211
|
category,
|
|
202
212
|
event: `${category}.start`,
|
|
203
|
-
message: `${options.action
|
|
213
|
+
message: `${mutationVerb(options.action)} ${options.displayTarget ?? options.target}`,
|
|
204
214
|
})
|
|
205
215
|
pumpQueue()
|
|
206
216
|
return task
|
|
@@ -215,16 +225,17 @@ function pumpQueue(): void {
|
|
|
215
225
|
task.status = 'running'
|
|
216
226
|
void runPluginMutation({ ...options, task }).finally(() => {
|
|
217
227
|
runningChildren.delete(task.id)
|
|
218
|
-
//
|
|
228
|
+
// 系统日志:任务终态(成功/失败/取消),按动作归类安装/卸载/更新日志
|
|
219
229
|
const show = task.displayTarget ?? task.target
|
|
220
|
-
const category = task.action
|
|
230
|
+
const category = mutationCategory(task.action)
|
|
231
|
+
const verb = (v: '成功' | '失败') => task.action === 'remove' ? `卸载${v}` : task.action === 'update' ? `更新${v}` : `安装${v}`
|
|
221
232
|
if (task.status === 'done') {
|
|
222
233
|
appendLog(options.profile, {
|
|
223
234
|
at: Date.now(),
|
|
224
235
|
level: 'success',
|
|
225
236
|
category,
|
|
226
237
|
event: `${category}.done`,
|
|
227
|
-
message: `${
|
|
238
|
+
message: `${verb('成功')} ${show}${task.needsRestart ? '(待重启生效)' : ''}`,
|
|
228
239
|
})
|
|
229
240
|
} else if (task.status === 'failed') {
|
|
230
241
|
appendLog(options.profile, {
|
|
@@ -232,7 +243,7 @@ function pumpQueue(): void {
|
|
|
232
243
|
level: 'error',
|
|
233
244
|
category,
|
|
234
245
|
event: `${category}.fail`,
|
|
235
|
-
message: `${
|
|
246
|
+
message: `${verb('失败')} ${show}`,
|
|
236
247
|
})
|
|
237
248
|
} else if (task.status === 'cancelled') {
|
|
238
249
|
appendLog(options.profile, {
|
|
@@ -256,7 +267,7 @@ function pumpQueue(): void {
|
|
|
256
267
|
* Never rejects; failures surface through the result / task state.
|
|
257
268
|
*/
|
|
258
269
|
function spawnMutation(options: {
|
|
259
|
-
action: 'add' | 'remove'
|
|
270
|
+
action: 'add' | 'remove' | 'update'
|
|
260
271
|
profile: string
|
|
261
272
|
target: string
|
|
262
273
|
timeoutMs?: number
|
|
@@ -280,17 +291,21 @@ function spawnMutation(options: {
|
|
|
280
291
|
// 更新已安装的 npm 包:显式指定 @latest。pnpm add <pkg>(无版本)对已存在依赖
|
|
281
292
|
// (尤其精确 spec 如 "1.3.0")是幂等的 —— 输出 "Already up to date" 且不改 package.json,
|
|
282
293
|
// 导致「更新成功」但实际仍停留在旧版本;git 地址与全局安装不受此影响。
|
|
283
|
-
|
|
294
|
+
// update 动作实际执行 `add <target>@latest`(pnpm 对已存在依赖带显式版本即原位覆盖升级),
|
|
295
|
+
// CLI 动词仍透传 add —— 不执行官方 update 动词,避免与既有覆盖重装机制分叉。
|
|
296
|
+
const isUpdate = action === 'update' || updateNpm === true
|
|
297
|
+
const cliAction = action === 'update' ? 'add' : action
|
|
298
|
+
const npmUpdateTarget = isUpdate && !isGlobalNpm && githubRepoOf(target) === null
|
|
284
299
|
? `${target}@latest`
|
|
285
300
|
: target
|
|
286
301
|
const args = isGlobalNpm
|
|
287
302
|
? ['install', '-g', ...(globalNpm ?? [])]
|
|
288
|
-
: [...invocation.prefixArgs, 'plugin', '--profile', profile,
|
|
303
|
+
: [...invocation.prefixArgs, 'plugin', '--profile', profile, cliAction, npmUpdateTarget]
|
|
289
304
|
// 记录实际执行的命令(npm 通道显示包名,git 通道显示 HTTPS 源),
|
|
290
305
|
// 失败提 Issue 时作为「已尝试的安装方式」随附
|
|
291
306
|
const executed = isGlobalNpm
|
|
292
307
|
? `npm install -g ${(globalNpm ?? []).join(' ')}`
|
|
293
|
-
: `dsh plugin --profile ${profile} ${
|
|
308
|
+
: `dsh plugin --profile ${profile} ${cliAction} ${npmUpdateTarget}`
|
|
294
309
|
if (task && !task.attempts.includes(executed)) {
|
|
295
310
|
task.attempts.push(executed)
|
|
296
311
|
}
|
|
@@ -443,10 +458,10 @@ function spawnMutation(options: {
|
|
|
443
458
|
if (removed) clearPendingRestart(displayTarget ?? target)
|
|
444
459
|
else addPendingRestart(displayTarget ?? target, 'uninstall')
|
|
445
460
|
} else {
|
|
446
|
-
//
|
|
461
|
+
// 安装/更新成功(或卸载但无 loader 引用)→ 登记待重启:
|
|
447
462
|
// 插件要宿主重启才会挂载/卸载干净,重启前一直提醒;展示目标用 displayTarget(owner/repo)
|
|
448
463
|
task.needsRestart = true
|
|
449
|
-
addPendingRestart(displayTarget ?? target, action === '
|
|
464
|
+
addPendingRestart(displayTarget ?? target, action === 'remove' ? 'uninstall' : 'install')
|
|
450
465
|
}
|
|
451
466
|
}
|
|
452
467
|
pushLine(task, result.timedOut ? '[timed out]' : `[exit ${result.exitCode ?? '?'}]`)
|
|
@@ -513,11 +528,11 @@ function ignoredBuildKeys(output: string): string[] {
|
|
|
513
528
|
* 前端据此展示「本机 npm 版本过低」的准确原因 —— 避免该报错被外层 ERR_PNPM_PREPARE_PACKAGE
|
|
514
529
|
* 误判成插件打包/分发问题、误导用户提 Issue。检测逻辑在独立的 npm-check.ts(本机 npm 环境职责)。 */
|
|
515
530
|
function annotateNpmTooLow(result: InstallResult, options: {
|
|
516
|
-
action: 'add' | 'remove'
|
|
531
|
+
action: 'add' | 'remove' | 'update'
|
|
517
532
|
task?: InstallTask
|
|
518
533
|
env?: NodeJS.ProcessEnv
|
|
519
534
|
}): InstallResult {
|
|
520
|
-
if (options.action
|
|
535
|
+
if (options.action === 'remove' || result.exitCode === 0 || result.timedOut) return result
|
|
521
536
|
const line = npmTooLowMarker(`${result.stderr}\n${result.stdout}`, options.env)
|
|
522
537
|
if (line === null) return result
|
|
523
538
|
if (options.task && options.task.status !== 'cancelled') pushLine(options.task, line)
|
|
@@ -534,7 +549,7 @@ function annotateNpmTooLow(result: InstallResult, options: {
|
|
|
534
549
|
* Other failures fall through to the single-spawn result.
|
|
535
550
|
*/
|
|
536
551
|
export async function runPluginMutation(options: {
|
|
537
|
-
action: 'add' | 'remove'
|
|
552
|
+
action: 'add' | 'remove' | 'update'
|
|
538
553
|
profile: string
|
|
539
554
|
target: string
|
|
540
555
|
timeoutMs?: number
|
|
@@ -550,7 +565,8 @@ export async function runPluginMutation(options: {
|
|
|
550
565
|
let result = await spawnMutation(options)
|
|
551
566
|
// allowBuilds 放行 / 装后入口校验都是 profile 专属(pnpm 装进 profile 的场景);
|
|
552
567
|
// 全局 npm 安装直接走 npm,无 pnpm allowBuilds 拦截、也不进 profile,跳过这两类恢复
|
|
553
|
-
|
|
568
|
+
// 安装与更新(同为 add 通道执行)共享同一套恢复路径;卸载不适用
|
|
569
|
+
if (options.action !== 'remove' && result.exitCode !== 0 && !result.timedOut && (options.globalNpm ?? []).length === 0) {
|
|
554
570
|
const output = `${result.stderr}\n${result.stdout}`
|
|
555
571
|
// git 源:pnpm 打印的 key@url 提示
|
|
556
572
|
if (output.includes('ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED')) {
|
|
@@ -588,7 +604,7 @@ export async function runPluginMutation(options: {
|
|
|
588
604
|
// pnpm 安装本身成功,但目标包入口文件缺失(git 分发缺构建产物)→ 判为失败:
|
|
589
605
|
// 撤销刚登记的待重启,避免用户重启时宿主加载残缺包直接崩溃。
|
|
590
606
|
// 全局 npm 安装不进 profile(profile deps 里没有这些包),跳过入口校验
|
|
591
|
-
if (options.action
|
|
607
|
+
if (options.action !== 'remove' && result.exitCode === 0 && !result.timedOut && (options.globalNpm ?? []).length === 0) {
|
|
592
608
|
const verified = verifyInstalledEntry(options.profile, options.target)
|
|
593
609
|
if (verified.name !== null && verified.missing !== null) {
|
|
594
610
|
clearPendingRestart(options.displayTarget ?? options.target)
|
|
@@ -30,17 +30,18 @@ export function githubTarget(repo: string): string | null {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* DSH 命令行前缀(官方唯一形式 `dsh plugin --profile <name> add <target>`)—— 提取其中的安装目标;
|
|
33
|
+
* DSH 命令行前缀(官方唯一形式 `dsh plugin --profile <name> <add|update> <target>`)—— 提取其中的安装目标;
|
|
34
34
|
* 非命令输入原样返回。兼容「直接粘贴完整命令」(防用户输错):--profile 必填且只有长选项
|
|
35
35
|
* (官方 CLI 无 -p 简写,省略即报错),支持 `--profile=<name>` 等号形式,命令大小写不敏感。
|
|
36
|
+
* update = 更新已安装目标到最新;remove 不在此列(卸载走 /uninstall 路由,按已装包名直卸)。
|
|
36
37
|
* 与客户端 CustomInstallView 口径一致。
|
|
37
38
|
*/
|
|
38
|
-
const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+add\s+(.+)$/i
|
|
39
|
+
const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+(add|update)\s+(.+)$/i
|
|
39
40
|
|
|
40
41
|
export function installTargetOf(value: string): string {
|
|
41
42
|
const input = typeof value === 'string' ? value.trim() : ''
|
|
42
43
|
const match = DSH_PLUGIN_CMD_RE.exec(input)
|
|
43
|
-
return match !== null ? match[
|
|
44
|
+
return match !== null ? match[2].trim() : input
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|