dsh-plugin 1.0.0 → 1.1.1
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 +12 -7
- package/README.md +12 -7
- package/client/client.js +725 -223
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +43 -7
- package/lib/services/installed-versions.js +16 -1
- package/lib/services/loader.js +12 -5
- package/lib/services/npm-resolve.js +67 -0
- package/lib/services/task-queue.js +55 -10
- package/lib/types/services/install-types.d.ts +8 -0
- package/lib/types/services/installed-versions.d.ts +10 -0
- package/lib/types/services/loader.d.ts +6 -4
- package/lib/types/services/npm-resolve.d.ts +6 -0
- package/lib/types/services/task-queue.d.ts +11 -5
- package/package.json +1 -1
- package/src/client/components/AboutModal.tsx +72 -0
- package/src/client/components/CatalogHeader.tsx +30 -5
- package/src/client/components/HubUpdateModal.tsx +84 -0
- package/src/client/components/NotificationsModal.tsx +84 -58
- package/src/client/components/PluginCard.tsx +3 -0
- package/src/client/components/PluginHubSection.tsx +73 -14
- package/src/client/components/modals.tsx +42 -18
- package/src/client/hooks/useCatalog.ts +96 -18
- package/src/client/hooks/useTaskQueue.ts +164 -54
- package/src/client/lib/catalog.ts +81 -13
- package/src/client/lib/failures.ts +14 -2
- package/src/client/lib/renderMarkdown.ts +116 -0
- package/src/client/locales.ts +47 -4
- package/src/client/styles/Header.module.css +83 -3
- package/src/client/styles/List.module.css +4 -4
- package/src/client/styles/Modal.module.css +320 -5
- package/src/client/styles/tokens.module.css +8 -3
- package/src/client/types.ts +3 -1
- package/src/server/http/routes.ts +42 -7
- package/src/server/services/install-types.ts +8 -0
- package/src/server/services/installed-versions.ts +20 -1
- package/src/server/services/loader.ts +12 -5
- package/src/server/services/npm-resolve.ts +67 -0
- package/src/server/services/task-queue.ts +57 -10
|
@@ -36,20 +36,25 @@ export interface ActiveTaskInfo {
|
|
|
36
36
|
status: 'pending' | 'running'
|
|
37
37
|
progress: number
|
|
38
38
|
lines: string[]
|
|
39
|
+
/** 尝试过的安装方式(npm 反查 + 实际执行命令):失败时前端 issue 预填需要 */
|
|
40
|
+
attempts: string[]
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/** All non-terminal tasks in queue order (running first, then pending). Lets the client resume a queue after a page reload. */
|
|
42
44
|
export function activeTask(): ActiveTaskInfo[] {
|
|
43
45
|
const found: ActiveTaskInfo[] = []
|
|
46
|
+
// 展示目标用 displayTarget(owner/repo):安装 target 可能被服务端切成 npm 包名
|
|
47
|
+
// (npm 反查命中),队列恢复/待重启展示对用户保持仓库名无感知
|
|
48
|
+
const show = (task: InstallTask): string => task.displayTarget ?? task.target
|
|
44
49
|
for (const item of queue) {
|
|
45
50
|
const task = item.task
|
|
46
51
|
if (task.status === 'pending') {
|
|
47
|
-
found.push({ id: task.id, target: task
|
|
52
|
+
found.push({ id: task.id, target: show(task), action: task.action, status: 'pending', progress: task.progress, lines: task.lines.slice(0, MAX_TASK_LINES), attempts: task.attempts })
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
tasks.forEach((task) => {
|
|
51
56
|
if (task.status === 'running') {
|
|
52
|
-
found.unshift({ id: task.id, target: task
|
|
57
|
+
found.unshift({ id: task.id, target: show(task), action: task.action, status: 'running', progress: task.progress, lines: task.lines.slice(0, MAX_TASK_LINES), attempts: task.attempts })
|
|
53
58
|
}
|
|
54
59
|
})
|
|
55
60
|
return found
|
|
@@ -162,8 +167,10 @@ export function startPluginMutation(options: {
|
|
|
162
167
|
displayTarget?: string
|
|
163
168
|
/** 运行中 loader:卸载成功后主动移除条目、立即生效(缺失时卸载仍需重启清理) */
|
|
164
169
|
uninstallLoader?: LoaderHandle
|
|
170
|
+
/** 入队前已尝试的安装方式(npm registry 反查等):失败提 Issue 时如实展示;实际执行的命令由 spawn 时追加 */
|
|
171
|
+
attempts?: string[]
|
|
165
172
|
}): InstallTask {
|
|
166
|
-
const task: InstallTask = { id: nextTaskId, target: options.target, action: options.action, status: 'pending', timedOut: false, exitCode: null, progress: 0, lines: [] }
|
|
173
|
+
const task: InstallTask = { id: nextTaskId, target: options.target, displayTarget: options.displayTarget, action: options.action, status: 'pending', timedOut: false, exitCode: null, progress: 0, lines: [], attempts: options.attempts ?? [], needsRestart: false }
|
|
167
174
|
nextTaskId = nextTaskId >= Number.MAX_SAFE_INTEGER ? 1 : nextTaskId + 1
|
|
168
175
|
tasks.set(task.id, task)
|
|
169
176
|
if (tasks.size > MAX_TASKS) {
|
|
@@ -216,6 +223,12 @@ function spawnMutation(options: {
|
|
|
216
223
|
const { action, profile, target, timeoutMs = 5 * 60 * 1000, env, task, displayTarget, uninstallLoader } = options
|
|
217
224
|
const invocation = cliInvocation()
|
|
218
225
|
const args = [...invocation.prefixArgs, 'plugin', '--profile', profile, action, target]
|
|
226
|
+
// 记录实际执行的命令(npm 通道显示包名,git 通道显示 github: 源):失败提 Issue 时
|
|
227
|
+
// 「已尝试的安装方式」如实展示完整命令链,作者一眼看清我们跑过什么
|
|
228
|
+
const executed = `dsh plugin --profile ${profile} ${action} ${target}`
|
|
229
|
+
if (task && !task.attempts.includes(executed)) {
|
|
230
|
+
task.attempts.push(executed)
|
|
231
|
+
}
|
|
219
232
|
return new Promise((resolvePromise) => {
|
|
220
233
|
let stdout = ''
|
|
221
234
|
let stderr = ''
|
|
@@ -332,14 +345,19 @@ function spawnMutation(options: {
|
|
|
332
345
|
if (task.status === 'done') {
|
|
333
346
|
task.progress = 100
|
|
334
347
|
if (action === 'remove' && uninstallLoader) {
|
|
335
|
-
// 卸载成功 → 主动从运行中 loader
|
|
336
|
-
//
|
|
348
|
+
// 卸载成功 → 主动从运行中 loader 停用该包条目;
|
|
349
|
+
// 仅当宿主从未加载过它(磁盘已干净)才判「无需重启」;
|
|
350
|
+
// 只要曾在 loader 中存活(停用过 live entry)就仍要求重启——
|
|
351
|
+
// 带 UI 的插件(侧边栏面板等宿主启动时渲染的槽位)disable 后不会
|
|
352
|
+
// 主动消失,重启才能立即摘除面板
|
|
337
353
|
const removed = await removeLoadedEntry(uninstallLoader, target)
|
|
354
|
+
task.needsRestart = !removed
|
|
338
355
|
if (removed) clearPendingRestart(displayTarget ?? target)
|
|
339
356
|
else addPendingRestart(displayTarget ?? target, 'uninstall')
|
|
340
357
|
} else {
|
|
341
358
|
// 安装成功(或卸载但无 loader 引用)→ 登记待重启:
|
|
342
359
|
// 插件要宿主重启才会挂载/卸载干净,重启前一直提醒;展示目标用 displayTarget(owner/repo)
|
|
360
|
+
task.needsRestart = true
|
|
343
361
|
addPendingRestart(displayTarget ?? target, action === 'add' ? 'install' : 'uninstall')
|
|
344
362
|
}
|
|
345
363
|
}
|
|
@@ -393,12 +411,24 @@ export function verifyInstalledEntry(profile: string, target: string): { name: s
|
|
|
393
411
|
}
|
|
394
412
|
}
|
|
395
413
|
|
|
414
|
+
/** 从 pnpm `Ignored build scripts:` 提示行解析被拦截的构建脚本 key(name@version,逗号分隔)。 */
|
|
415
|
+
function ignoredBuildKeys(output: string): string[] {
|
|
416
|
+
const m = /Ignored build scripts:\s*(.+)/i.exec(output)
|
|
417
|
+
if (m === null) return []
|
|
418
|
+
return m[1]
|
|
419
|
+
.split(',')
|
|
420
|
+
.map((s) => s.trim())
|
|
421
|
+
.filter((s) => s !== '')
|
|
422
|
+
}
|
|
423
|
+
|
|
396
424
|
/**
|
|
397
|
-
* Run a plugin mutation with one recovery path: when `add` fails because
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
425
|
+
* Run a plugin mutation with one recovery path: when `add` fails because
|
|
426
|
+
* pnpm's allowBuilds gate blocks a build script — either the git-hosted
|
|
427
|
+
* package's own `prepare` (`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`, key from
|
|
428
|
+
* the printed key@url hint) or a dependency's native build
|
|
429
|
+
* (`ERR_PNPM_IGNORED_BUILDS`, key = the listed name@version) — write the
|
|
430
|
+
* exact keys into the profile's pnpm-workspace.yaml and retry once.
|
|
431
|
+
* Other failures fall through to the single-spawn result.
|
|
402
432
|
*/
|
|
403
433
|
export async function runPluginMutation(options: {
|
|
404
434
|
action: 'add' | 'remove'
|
|
@@ -415,6 +445,7 @@ export async function runPluginMutation(options: {
|
|
|
415
445
|
const result = await spawnMutation(options)
|
|
416
446
|
if (options.action === 'add' && result.exitCode !== 0 && !result.timedOut) {
|
|
417
447
|
const output = `${result.stderr}\n${result.stdout}`
|
|
448
|
+
// git 源:pnpm 打印的 key@url 提示
|
|
418
449
|
if (output.includes('ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED')) {
|
|
419
450
|
const key = parseAllowBuildsKey(output)
|
|
420
451
|
if (key !== null) {
|
|
@@ -429,6 +460,22 @@ export async function runPluginMutation(options: {
|
|
|
429
460
|
return spawnMutation(options)
|
|
430
461
|
}
|
|
431
462
|
}
|
|
463
|
+
// 依赖原生模块/构建脚本被 pnpm v11 拦截(ERR_PNPM_IGNORED_BUILDS):git 与 npm 源都会出现。
|
|
464
|
+
// allowBuilds 的 key 是输出里列出的 name@version(如 node-pty@1.1.0),逐个放行后重试一次;
|
|
465
|
+
// 输出解析不到时回退用目标名(插件自身构建脚本被拦的 npm 场景)。
|
|
466
|
+
if (output.includes('ERR_PNPM_IGNORED_BUILDS')) {
|
|
467
|
+
const keys = ignoredBuildKeys(output)
|
|
468
|
+
if (keys.length === 0 && !options.target.startsWith('github:')) keys.push(options.target)
|
|
469
|
+
if (keys.length > 0) {
|
|
470
|
+
if (options.task) {
|
|
471
|
+
options.task.status = 'running'
|
|
472
|
+
options.task.exitCode = null
|
|
473
|
+
pushLine(options.task, `[allow build] ${keys.join(', ')}`)
|
|
474
|
+
}
|
|
475
|
+
for (const key of keys) addAllowBuildsKey(options.profile, key)
|
|
476
|
+
return spawnMutation(options)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
432
479
|
}
|
|
433
480
|
// pnpm 安装本身成功,但目标包入口文件缺失(git 分发缺构建产物)→ 判为失败:
|
|
434
481
|
// 撤销刚登记的待重启,避免用户重启时宿主加载残缺包直接崩溃
|