@noob-stupid/dsh-plugin-console 0.5.42 → 0.5.43
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.md +12 -12
- package/README.zh.md +12 -0
- package/lib/server/domain/ai-run.js +6 -3
- package/lib/server/infra/exec.js +17 -5
- package/lib/server/infra/pnpm-runner-pick.js +130 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,18 @@
|
|
|
12
12
|
A framework changed by **any** update channel is still guarded. A version the host cannot
|
|
13
13
|
take is refused, not installed.
|
|
14
14
|
|
|
15
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
16
|
+
[](https://awesome-dsh-plugin.com)
|
|
17
|
+
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/stargazers)
|
|
18
|
+
[](LICENSE)
|
|
19
|
+
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/commits/main)
|
|
20
|
+
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/actions/workflows/registry.yml)
|
|
21
|
+
[](https://github.com/topics/dsh-plugin)
|
|
22
|
+
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
23
|
+
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
24
|
+
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/releases)[](https://www.dsh.so/artifact/dsh-plugin-hub)
|
|
25
|
+
[](https://www.dsh.so/artifact/dsh-plugin-hub/)
|
|
26
|
+
|
|
15
27
|
**Install (npm, one line)**
|
|
16
28
|
|
|
17
29
|
```sh
|
|
@@ -42,18 +54,6 @@ Then restart the dsh service → refresh the page → **Settings → Plugins →
|
|
|
42
54
|
as `link:`. Install scripts are never auto-authorized, and pnpm's supply-chain gate is never
|
|
43
55
|
bypassed.
|
|
44
56
|
|
|
45
|
-
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
46
|
-
[](https://awesome-dsh-plugin.com)
|
|
47
|
-
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/stargazers)
|
|
48
|
-
[](LICENSE)
|
|
49
|
-
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/commits/main)
|
|
50
|
-
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/actions/workflows/registry.yml)
|
|
51
|
-
[](https://github.com/topics/dsh-plugin)
|
|
52
|
-
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
53
|
-
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
54
|
-
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/releases)[](https://www.dsh.so/artifact/dsh-plugin-hub)
|
|
55
|
-
[](https://www.dsh.so/artifact/dsh-plugin-hub/)
|
|
56
|
-
|
|
57
57
|
<img width="1170" height="609" alt="DSH Plugin Gating Hub" src="https://github.com/user-attachments/assets/b802d606-14ba-4151-9956-ff642ed12b0a" />
|
|
58
58
|
|
|
59
59
|
> **Discovery layer on top**: a **built-in multi-source plugin market & index** (500+ plugins /
|
package/README.zh.md
CHANGED
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
**DSH 的框架升级安全 + 插件升级门控。** 升级失败自动回滚。**别的通道**改了框架也照样守门。
|
|
12
12
|
宿主带不动的插件版本直接拒绝,不会装上。
|
|
13
13
|
|
|
14
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
15
|
+
[](https://awesome-dsh-plugin.com)
|
|
16
|
+
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/stargazers)
|
|
17
|
+
[](LICENSE)
|
|
18
|
+
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/commits/main)
|
|
19
|
+
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/actions/workflows/registry.yml)
|
|
20
|
+
[](https://github.com/topics/dsh-plugin)
|
|
21
|
+
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
22
|
+
[](https://www.npmjs.com/package/@noob-stupid/dsh-plugin-console)
|
|
23
|
+
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/releases)[](https://www.dsh.so/artifact/dsh-plugin-hub)
|
|
24
|
+
[](https://www.dsh.so/artifact/dsh-plugin-hub/)
|
|
25
|
+
|
|
14
26
|
**安装(npm,一行)**
|
|
15
27
|
|
|
16
28
|
```sh
|
|
@@ -13,7 +13,7 @@ import { frameworkCheckPromptText } from './compat.js'
|
|
|
13
13
|
import { compFind, compUpsert } from './components.js'
|
|
14
14
|
import { jobLog, waitHealth } from './jobs.js'
|
|
15
15
|
import { gitCloneUrls } from './sources.js'
|
|
16
|
-
import { buildPnpmEnv, execFileAsync, execFileWithKillTree, gitEnv,
|
|
16
|
+
import { buildPnpmEnv, execFileAsync, execFileWithKillTree, gitEnv, pnpmRunnersFor, runPnpmWithFallback } from '../infra/exec.js'
|
|
17
17
|
import { maskUrl } from '../infra/mask.js'
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -42,8 +42,9 @@ async function aiRepair(job, ports, profileDir, candidates, lastError) {
|
|
|
42
42
|
return
|
|
43
43
|
}
|
|
44
44
|
// 跨平台提示:corepack 的位置随安装方式/系统而异(Windows 官方安装器在 <node bin>/node_modules,
|
|
45
|
-
// Linux 的 npm 全局在 <prefix>/lib/node_modules
|
|
46
|
-
|
|
45
|
+
// Linux 的 npm 全局在 <prefix>/lib/node_modules),所以给子代理的提示不再写死路径。
|
|
46
|
+
// 2026-10-08:首选执行方式按**该 profile 声明的 pnpm 版本**排(唯一判据 pnpmRunnersFor)。
|
|
47
|
+
const corepackRunner = pnpmRunnersFor(profileDir)[0]
|
|
47
48
|
const prompt = [
|
|
48
49
|
'你是 DeepSeek Harness 的插件安装专家。用户通过插件管理面板安装插件失败,现在由你主动接管安装(不是被动"修复",而是像人工一样把它装好)。',
|
|
49
50
|
`目标仓库:${job.repo}`,
|
|
@@ -368,6 +369,8 @@ async function runAiStep(job, step, ports, profileDir, signal) {
|
|
|
368
369
|
// 2026-09-20 另一位用户的 install-npm 失败现场就是这一行),改为按优先级尝试可用的执行方式
|
|
369
370
|
const args = ['add', pkg, '--dir', dir, '--registry', 'https://registry.npmmirror.com']
|
|
370
371
|
const { runner } = await runPnpmWithFallback(args, {
|
|
372
|
+
// 目标 profile 明确给出(这条分支不设 cwd,靠 `--dir` 指目录),供版本匹配首选执行方式
|
|
373
|
+
profileDir: dir,
|
|
371
374
|
// 2026-09-26:与安装主链路同一套 pnpm env(fetch 超时/重试 + CI=true 非交互),见 infra/exec.js
|
|
372
375
|
execOpts: { timeout: 900000, windowsHide: true, maxBuffer: 4 * 1024 * 1024, env: buildPnpmEnv('https://registry.npmmirror.com'), signal },
|
|
373
376
|
})
|
package/lib/server/infra/exec.js
CHANGED
|
@@ -6,6 +6,7 @@ import { promisify } from 'node:util'
|
|
|
6
6
|
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
7
7
|
import { dirname, join } from 'node:path'
|
|
8
8
|
import { homedir } from 'node:os'
|
|
9
|
+
import { selectPnpmRunners } from './pnpm-runner-pick.js'
|
|
9
10
|
|
|
10
11
|
/** git 可执行名(跨平台)。事故(2026-09-20,另一位用户:Android + proot Ubuntu):
|
|
11
12
|
* 「仓库落地」硬编码 `git.exe` → 非 Windows 环境 spawn git.exe ENOENT,克隆必然失败。 */
|
|
@@ -181,6 +182,12 @@ function resolvePnpmRunners({
|
|
|
181
182
|
return runners
|
|
182
183
|
}
|
|
183
184
|
|
|
185
|
+
/** 目标 profile 的 pnpm 执行方式:既有候选顺序 + 「版本一致者优先」(判据只此一份,见
|
|
186
|
+
* infra/pnpm-runner-pick.js);没给 profileDir 或读不出声明版本时,与 `resolvePnpmRunners()` 逐项相同。 */
|
|
187
|
+
function pnpmRunnersFor(profileDir, opts = {}) {
|
|
188
|
+
return selectPnpmRunners(resolvePnpmRunners(opts), { ...opts, profileDir })
|
|
189
|
+
}
|
|
190
|
+
|
|
184
191
|
/** 依次尝试各执行方式;只有"执行方式本身不可用"(ENOENT / MODULE_NOT_FOUND)才换下一个,
|
|
185
192
|
* 真正的安装失败(网络、依赖冲突等)立即抛出,并附上已尝试的清单便于排查。
|
|
186
193
|
*
|
|
@@ -190,8 +197,13 @@ function resolvePnpmRunners({
|
|
|
190
197
|
* 2026-09-26(本次):执行器由 `execFileAsync` 换成 `execFileWithKillTree`(**只换失败路径的实现**)——
|
|
191
198
|
* pnpm 的 fetch 超时以前只 kill 父进程,pnpm 派生的 git / tar / node-gyp / 子 pnpm 会变成孤儿继续跑,
|
|
192
199
|
* 占着 node_modules 与 .git 里的文件;现在超时/中断即杀整棵树。成功路径返回值与异常形状不变。
|
|
193
|
-
* `exec` 是给单测留的注入口(默认真实实现),生产调用方不需要传。
|
|
194
|
-
|
|
200
|
+
* `exec` 是给单测留的注入口(默认真实实现),生产调用方不需要传。
|
|
201
|
+
* `profileDir` 缺省取 `execOpts.cwd`;`runners` 只给单测/特殊调用点用(传了就完全按它来)。
|
|
202
|
+
* `runnerOpts` 是给离线单测注入 `resolvePnpmRunners` 的探针(exists/env/resourcesPath…),生产不传。 */
|
|
203
|
+
async function runPnpmWithFallback(args, { execOpts = {}, profileDir = execOpts.cwd, runners = null, runnerOpts = {}, exec = execFileWithKillTree } = {}) {
|
|
204
|
+
// 首选执行方式按**目标 profile 声明的 pnpm 版本**排(2026-10-08 改错,判据只有一份:infra/pnpm-runner-pick.js);
|
|
205
|
+
// 不传 runners 且没有 profileDir 时,与旧的 `runners = resolvePnpmRunners()` 逐项相同。
|
|
206
|
+
const candidates = Array.isArray(runners) ? runners : pnpmRunnersFor(profileDir, runnerOpts)
|
|
195
207
|
let lastError = null
|
|
196
208
|
const withStderr = (error) => {
|
|
197
209
|
const stderr = typeof error?.stderr === 'string' ? error.stderr.trim() : ''
|
|
@@ -207,7 +219,7 @@ async function runPnpmWithFallback(args, { execOpts = {}, runners = resolvePnpmR
|
|
|
207
219
|
}
|
|
208
220
|
return wrapped
|
|
209
221
|
}
|
|
210
|
-
for (const runner of
|
|
222
|
+
for (const runner of candidates) {
|
|
211
223
|
// 执行方式可以带自己的 spawn 附加项(2026-09-27 加法):
|
|
212
224
|
// · `verbatim`:cmd.exe 形态必须原样透传命令行(否则 Node 会把段内引号转义成 `\"`,见 cmdCommandLine);
|
|
213
225
|
// · `env`:桌面端形态必须带 ELECTRON_RUN_AS_NODE=1(否则 Electron 二进制会当 GUI 应用启动)。
|
|
@@ -230,7 +242,7 @@ async function runPnpmWithFallback(args, { execOpts = {}, runners = resolvePnpmR
|
|
|
230
242
|
if (!/ENOENT|Cannot find module/u.test(message)) throw lastError
|
|
231
243
|
}
|
|
232
244
|
}
|
|
233
|
-
const tried =
|
|
245
|
+
const tried = candidates.map((r) => r.note).join(' → ')
|
|
234
246
|
throw new Error(`${lastError?.message ?? 'pnpm 执行失败'}(已尝试:${tried})`)
|
|
235
247
|
}
|
|
236
248
|
|
|
@@ -583,4 +595,4 @@ const GH_BIN_CANDIDATES = [
|
|
|
583
595
|
join(homedir(), 'scoop', 'shims', 'gh.exe'),
|
|
584
596
|
]
|
|
585
597
|
|
|
586
|
-
export { GH_BIN_CANDIDATES, gitEnv, gitBin, processAlive, execFileAsync, resolvePnpmRunners, runPnpmWithFallback, killProcessTree, posixDescendants, execFileWithKillTree, KILL_WAIT_MS, KILL_WAIT_POLL_MS, GIT_LOW_SPEED_ENV, pnpmEnvOverrides, buildPnpmEnv, pnpmFetchArgs, pnpmAddArgs, unknownPnpmOption, runPnpmAdd, cmdCommandLine, cmdCorepackCommand, cmdShellArgv }
|
|
598
|
+
export { GH_BIN_CANDIDATES, gitEnv, gitBin, processAlive, execFileAsync, resolvePnpmRunners, pnpmRunnersFor, runPnpmWithFallback, killProcessTree, posixDescendants, execFileWithKillTree, KILL_WAIT_MS, KILL_WAIT_POLL_MS, GIT_LOW_SPEED_ENV, pnpmEnvOverrides, buildPnpmEnv, pnpmFetchArgs, pnpmAddArgs, unknownPnpmOption, runPnpmAdd, cmdCommandLine, cmdCorepackCommand, cmdShellArgv }
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// L0 · infra(边界由 tests/test-architecture-guard.mjs 断言)
|
|
2
|
+
//
|
|
3
|
+
// 「选哪个 pnpm 执行方式」的**唯一判据承担者**(2026-10-08 加法,只此一处,调用点不许各写一份)。
|
|
4
|
+
//
|
|
5
|
+
// 现场(官方桌面端 desktop profile,上一批实测结论):
|
|
6
|
+
// · 控制台 `resolvePnpmRunners()` 在普通 node 进程里第一命中 `node-corepack` → corepack 自己那版 pnpm
|
|
7
|
+
// (本机实测 11.21.0:corepack 的 lastKnownGood.json,见下);
|
|
8
|
+
// · 而该 profile 的 node_modules 是**桌面端自带运行时**那份 pnpm 建出来的
|
|
9
|
+
// (`<resources>\runtime\pnpm`,本机实测 11.7.0),`.modules.yaml` 的 packageManager 如实记着 `pnpm@11.7.0`。
|
|
10
|
+
// ⇒ 版本错配:新版 pnpm 判定链接/peer 状态漂移 ⇒ 重新导入 `@deepseek-ai` 的 schedule 包
|
|
11
|
+
// ⇒ Windows 上 rename 覆盖已存在目录 → `ERR_PNPM_EPERM` ⇒ 卡满 120s 被超时杀掉,
|
|
12
|
+
// 还留下 `<包名>_tmp_<pid>_<n>` 僵尸目录;换**匹配那份**(桌面端 11.7.0)同一操作 0.8–1.4 秒成功。
|
|
13
|
+
//
|
|
14
|
+
// 本模块只做一件事:把"版本与目标 profile 声明一致"的 runner **提到最前**,其余顺序逐项不变。
|
|
15
|
+
// · 匹配不到 / 版本读不出来 / 没给 profileDir ⇒ **原样返回既有顺序**(行为不倒退);
|
|
16
|
+
// · **不新增**"拒绝执行"分支:真正跑不起来照旧由 `runPnpmWithFallback` 如实抛出,
|
|
17
|
+
// 既有超时/回滚语义一个字不动。
|
|
18
|
+
// 版本判据全部是**只读静态读取**(不 spawn pnpm,不碰 profile 里的任何文件):
|
|
19
|
+
// · 目标版本 = `<profileDir>/node_modules/.modules.yaml` 的 `"packageManager": "pnpm@x.y.z"`;
|
|
20
|
+
// · `node-corepack` / `cmd-corepack` = corepack 自己的解析规则:项目 package.json 的 `packageManager`
|
|
21
|
+
// 字段优先,否则 corepack home 的 `lastKnownGood.json`(照抄 corepack 0.34.5
|
|
22
|
+
// `dist/lib/corepack.cjs#getCorepackHomeFolder / getDefaultVersion`);
|
|
23
|
+
// · `desktop-pnpm-mjs` / `path-pnpm-mjs` = `<…>/pnpm/bin/pnpm.mjs` 同级那份 pnpm 的 package.json version;
|
|
24
|
+
// · `cmd-pnpm` / `path-pnpm`(PATH 上的 shim/全局安装)版本不可静态判定 ⇒ null ⇒ 不参与匹配(绝不猜)。
|
|
25
|
+
import { readFileSync } from 'node:fs'
|
|
26
|
+
import { homedir } from 'node:os'
|
|
27
|
+
import { dirname, join } from 'node:path'
|
|
28
|
+
|
|
29
|
+
const PNPM_SPEC_RE = /^pnpm@(.+)$/u
|
|
30
|
+
|
|
31
|
+
/** 只读读取(任何异常都当"读不出来",绝不抛给调用方)。 */
|
|
32
|
+
function readText(readFile, path) {
|
|
33
|
+
try {
|
|
34
|
+
return String(readFile(path, 'utf8'))
|
|
35
|
+
} catch {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 目标 profile 声明/记录的 pnpm 版本(`node_modules/.modules.yaml` 的 packageManager)。
|
|
41
|
+
* 这就是"该用哪版 pnpm"的唯一事实来源:它是 pnpm 自己写下的、建出这棵 node_modules 的那版。 */
|
|
42
|
+
function modulesYamlPnpmVersion(profileDir, readFile = readFileSync) {
|
|
43
|
+
if (typeof profileDir !== 'string' || profileDir === '') return null
|
|
44
|
+
const text = readText(readFile, join(profileDir, 'node_modules', '.modules.yaml'))
|
|
45
|
+
return text === null ? null : text.match(/"packageManager"\s*:\s*"pnpm@([^"]+)"/u)?.[1] ?? null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 某个 package.json 的 `packageManager` 字段里的 pnpm 版本(corepack 的"项目声明优先"规则)。 */
|
|
49
|
+
function packageManagerPnpmVersion(dir, readFile = readFileSync) {
|
|
50
|
+
if (typeof dir !== 'string' || dir === '') return null
|
|
51
|
+
const text = readText(readFile, join(dir, 'package.json'))
|
|
52
|
+
if (text === null) return null
|
|
53
|
+
try {
|
|
54
|
+
return String(JSON.parse(text).packageManager ?? '').match(PNPM_SPEC_RE)?.[1] ?? null
|
|
55
|
+
} catch {
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** corepack home 目录(逐字照抄 corepack 0.34.5 的 getCorepackHomeFolder)。 */
|
|
61
|
+
function corepackHomeDir({ env = process.env, home = homedir(), platform = process.platform } = {}) {
|
|
62
|
+
const explicit = env?.COREPACK_HOME
|
|
63
|
+
if (typeof explicit === 'string' && explicit !== '') return explicit
|
|
64
|
+
const cache = env?.XDG_CACHE_HOME ?? env?.LOCALAPPDATA ?? join(home, platform === 'win32' ? 'AppData/Local' : '.cache')
|
|
65
|
+
return join(cache, 'node', 'corepack')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** `node <corepack.js> pnpm` 会跑哪一版 pnpm:项目 packageManager 字段优先,
|
|
69
|
+
* 否则 corepack 的 `lastKnownGood.json`(值形如 `11.21.0+sha512.…`,取 `+` 之前那截)。 */
|
|
70
|
+
function corepackPnpmVersion({ profileDir = null, readFile = readFileSync, env = process.env, home = homedir(), platform = process.platform } = {}) {
|
|
71
|
+
const declared = packageManagerPnpmVersion(profileDir, readFile)
|
|
72
|
+
if (declared !== null) return declared
|
|
73
|
+
const text = readText(readFile, join(corepackHomeDir({ env, home, platform }), 'lastKnownGood.json'))
|
|
74
|
+
if (text === null) return null
|
|
75
|
+
try {
|
|
76
|
+
const value = String(JSON.parse(text).pnpm ?? '')
|
|
77
|
+
return value.split('+')[0] === '' ? null : value.split('+')[0]
|
|
78
|
+
} catch {
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** `<…>/pnpm/bin/pnpm.mjs` 那份 pnpm 自己的版本(读它上一层目录的 package.json)。 */
|
|
84
|
+
function mjsPnpmVersion(mjsPath, readFile = readFileSync) {
|
|
85
|
+
if (typeof mjsPath !== 'string' || mjsPath === '') return null
|
|
86
|
+
const text = readText(readFile, join(dirname(dirname(mjsPath)), 'package.json'))
|
|
87
|
+
if (text === null) return null
|
|
88
|
+
try {
|
|
89
|
+
const version = String(JSON.parse(text).version ?? '')
|
|
90
|
+
return version === '' ? null : version
|
|
91
|
+
} catch {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 某个 runner 会跑哪一版 pnpm(null = 读不出来 ⇒ 不参与匹配,绝不猜)。 */
|
|
97
|
+
function runnerPnpmVersion(runner, opts = {}) {
|
|
98
|
+
const kind = String(runner?.kind ?? '')
|
|
99
|
+
if (kind === 'node-corepack' || kind === 'cmd-corepack') return corepackPnpmVersion(opts)
|
|
100
|
+
if (kind.endsWith('-pnpm-mjs')) {
|
|
101
|
+
let mjs = null
|
|
102
|
+
try {
|
|
103
|
+
// run([]) 只是"生成命令行"的纯函数(不 spawn);两种形态的 argv 里都带 <…>\pnpm\bin\pnpm.mjs
|
|
104
|
+
mjs = runner.run([]).argv.find((a) => typeof a === 'string' && a.endsWith('pnpm.mjs')) ?? null
|
|
105
|
+
} catch {
|
|
106
|
+
mjs = null
|
|
107
|
+
}
|
|
108
|
+
return mjsPnpmVersion(mjs, opts.readFile)
|
|
109
|
+
}
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 「选哪个 runner」的唯一判据:与目标 profile 声明版本一致的 runner 提到最前,其余相对顺序不变。
|
|
114
|
+
* 返回的可能是**同一个数组**(无可提升时零拷贝、零顺序变化)。
|
|
115
|
+
* `runners` 由 infra/exec.js#resolvePnpmRunners 产出;`profileDir` 就是这次 pnpm 操作的目标 profile。 */
|
|
116
|
+
function selectPnpmRunners(runners, { profileDir = null, readFile = readFileSync, env = process.env, home = homedir(), platform = process.platform } = {}) {
|
|
117
|
+
const list = Array.isArray(runners) ? runners : []
|
|
118
|
+
const target = modulesYamlPnpmVersion(profileDir, readFile)
|
|
119
|
+
if (target === null || list.length < 2) return list
|
|
120
|
+
const opts = { profileDir, readFile, env, home, platform }
|
|
121
|
+
const hit = list.find((runner) => runnerPnpmVersion(runner, opts) === target)
|
|
122
|
+
// 匹配不到、或本来就排在首位 ⇒ 与既有顺序逐项相同(不倒退、不新增拒绝分支)
|
|
123
|
+
if (hit === undefined || list[0] === hit) return list
|
|
124
|
+
return [hit, ...list.filter((r) => r !== hit)]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
corepackHomeDir, corepackPnpmVersion, mjsPnpmVersion, modulesYamlPnpmVersion,
|
|
129
|
+
packageManagerPnpmVersion, runnerPnpmVersion, selectPnpmRunners,
|
|
130
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noob-stupid/dsh-plugin-console",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.43",
|
|
4
4
|
"description": "DSH 框架升级安全与插件升级门控:一键升级、失败自动回滚、升级后回滚上版、旧插件不适配自动禁用;内置多源插件市场为发现层,插件源全部可自定义,可指向公司内网私有源 / 私有索引 / 本地 Git 仓库,纯内网离线可用 | Framework upgrade safety & plugin version gating for DSH, with a customizable multi-source plugin market: point every source at internal mirrors or a local file:// repo for intranet-only, offline installs.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|