@noob-stupid/dsh-plugin-console 0.3.16 → 0.3.18
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/index.js +94 -15
- package/package.json +39 -39
package/README.md
CHANGED
|
@@ -166,7 +166,7 @@ Requires: DSH ≥ 0.1.0-rc.6 (web profile, with `dsh-client-modules` / `dsh-host
|
|
|
166
166
|
- **Real-time progress**: a `DSH-Upgrade` console window pops up showing live pnpm download
|
|
167
167
|
progress; the in-panel progress card shows the waiting time;
|
|
168
168
|
- **Upgrade protection**: failed installs **auto-rollback** (robocopy, backup verified before
|
|
169
|
-
upgrade), version check catches fake success,
|
|
169
|
+
upgrade), version check catches fake success, 15-min hard timeout, stall detection
|
|
170
170
|
(no debug-log updates → auto switch registry), global trap fallback, and 15-min stale-state
|
|
171
171
|
cleanup — the framework is never left broken;
|
|
172
172
|
- **pnpm channel**: npm-cli.js freezes at startup in the schtasks task environment (0-byte
|
package/README.zh.md
CHANGED
|
@@ -152,7 +152,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
|
|
|
152
152
|
- **实时进度**:升级时弹出 `DSH-Upgrade` 窗口,实时显示 pnpm 下载进度;面板内进度卡片
|
|
153
153
|
同步显示等待时长;
|
|
154
154
|
- **升级保护**:失败**自动回滚**(robocopy,升级前校验回滚点有效)、版本校验防假成功、
|
|
155
|
-
|
|
155
|
+
15 分钟硬超时、卡死检测(debug 日志无更新自动切换 registry)、全局异常兜底、
|
|
156
156
|
15 分钟残留状态清理——框架绝不处于损坏状态;
|
|
157
157
|
- **pnpm 通道**:npm-cli.js 在 schtasks 任务环境启动即卡死(debug 日志 0 字节、网络请求
|
|
158
158
|
都发不出);升级改用 `corepack pnpm`(实测 0.4s 秒启动、11.5s 装完 rc.8)+ **国内源
|
package/lib/index.js
CHANGED
|
@@ -236,7 +236,6 @@ function postJsonUrl(url, formBody) {
|
|
|
236
236
|
'user-agent': GITHUB_UA,
|
|
237
237
|
accept: 'application/json',
|
|
238
238
|
},
|
|
239
|
-
rejectUnauthorized: false,
|
|
240
239
|
timeout: 20000,
|
|
241
240
|
}, (res) => {
|
|
242
241
|
const chunks = []
|
|
@@ -377,6 +376,8 @@ function cleanupStalePackageDir(profileDir, packageName) {
|
|
|
377
376
|
|
|
378
377
|
/** 软件源配置文件(registry 列表,安装链按主→备依次尝试)。 */
|
|
379
378
|
const SOURCES_FILE = join(dshHome(), 'plugin-console-sources.json')
|
|
379
|
+
/** 敏感凭据单独落盘(避免与可分享配置混存):自定义搜索源 headers + Gitee secret/token。 */
|
|
380
|
+
const SOURCES_SECRETS_FILE = join(dshHome(), 'plugin-console-sources.secrets.json')
|
|
380
381
|
const DEFAULT_SOURCES = {
|
|
381
382
|
registries: [
|
|
382
383
|
{ id: 'npmmirror', name: 'npmmirror(国内镜像)', url: 'https://registry.npmmirror.com', primary: true },
|
|
@@ -425,6 +426,7 @@ function readSources() {
|
|
|
425
426
|
const defaults = JSON.parse(JSON.stringify(DEFAULT_SOURCES))
|
|
426
427
|
try {
|
|
427
428
|
const data = JSON.parse(readFileSync(SOURCES_FILE, 'utf8'))
|
|
429
|
+
const secrets = readSourceSecrets()
|
|
428
430
|
const registries = (Array.isArray(data.registries) ? data.registries : [])
|
|
429
431
|
.filter((r) => r && typeof r.url === 'string' && isAllowedSourceUrl(r.url))
|
|
430
432
|
.map((r) => ({
|
|
@@ -439,13 +441,18 @@ function readSources() {
|
|
|
439
441
|
return { id: String(s.id), name: String(s.name ?? s.id), type: 'builtin' }
|
|
440
442
|
}
|
|
441
443
|
if (s && typeof s.url === 'string' && s.url.includes('{q}')) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
+
let headers = {}
|
|
445
|
+
const secretHeaders = secrets.headers?.[s.id]
|
|
446
|
+
if (secretHeaders && typeof secretHeaders === 'object') {
|
|
447
|
+
headers = { ...secretHeaders }
|
|
448
|
+
} else if (Array.isArray(s.headers)) {
|
|
444
449
|
for (const h of s.headers) {
|
|
445
450
|
if (h && typeof h.name === 'string' && h.name !== '' && typeof h.value === 'string') {
|
|
446
451
|
headers[h.name] = h.value
|
|
447
452
|
}
|
|
448
453
|
}
|
|
454
|
+
} else if (s.headers && typeof s.headers === 'object') {
|
|
455
|
+
headers = { ...s.headers }
|
|
449
456
|
}
|
|
450
457
|
return {
|
|
451
458
|
id: String(s.id ?? `search-${Math.random().toString(36).slice(2, 8)}`).slice(0, 40),
|
|
@@ -458,16 +465,58 @@ function readSources() {
|
|
|
458
465
|
return null
|
|
459
466
|
})
|
|
460
467
|
.filter((s) => s !== null)
|
|
468
|
+
const giteeBase = readGiteeConfig(data)
|
|
469
|
+
const gitee = {
|
|
470
|
+
...giteeBase,
|
|
471
|
+
clientSecret: secrets.gitee?.clientSecret ?? giteeBase.clientSecret,
|
|
472
|
+
token: secrets.gitee?.token ?? giteeBase.token,
|
|
473
|
+
}
|
|
461
474
|
if (registries.length > 0) {
|
|
462
475
|
if (!registries.some((r) => r.primary)) registries[0].primary = true
|
|
463
|
-
return { registries, searchSources, gitee
|
|
476
|
+
return { registries, searchSources, gitee }
|
|
464
477
|
}
|
|
465
478
|
} catch {}
|
|
466
479
|
return defaults
|
|
467
480
|
}
|
|
468
481
|
|
|
482
|
+
function readSourceSecrets() {
|
|
483
|
+
try {
|
|
484
|
+
const data = JSON.parse(readFileSync(SOURCES_SECRETS_FILE, 'utf8'))
|
|
485
|
+
return data && typeof data === 'object' ? data : {}
|
|
486
|
+
} catch {
|
|
487
|
+
return {}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function writeSourceSecrets(secrets) {
|
|
492
|
+
const gitee = secrets.gitee ?? {}
|
|
493
|
+
const headers = secrets.headers ?? {}
|
|
494
|
+
const hasGitee = typeof gitee.clientSecret === 'string' && gitee.clientSecret !== '' || typeof gitee.token === 'string' && gitee.token !== ''
|
|
495
|
+
const hasHeaders = Object.keys(headers).some((id) => { const h = headers[id]; return h && typeof h === 'object' && Object.keys(h).length > 0 })
|
|
496
|
+
if (!hasGitee && !hasHeaders) {
|
|
497
|
+
try { rmSync(SOURCES_SECRETS_FILE, { force: true }) } catch {}
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
await writeFile(SOURCES_SECRETS_FILE, JSON.stringify({ gitee, headers }, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 })
|
|
501
|
+
}
|
|
469
502
|
async function writeSources(sources) {
|
|
470
|
-
|
|
503
|
+
const secrets = { gitee: {}, headers: {} }
|
|
504
|
+
const cleanSearch = (sources.searchSources ?? []).map((s) => {
|
|
505
|
+
if (!s) return s
|
|
506
|
+
if (s.headers && typeof s.headers === 'object' && Object.keys(s.headers).length > 0) {
|
|
507
|
+
secrets.headers[s.id] = { ...s.headers }
|
|
508
|
+
const { headers, ...rest } = s
|
|
509
|
+
return rest
|
|
510
|
+
}
|
|
511
|
+
return s
|
|
512
|
+
})
|
|
513
|
+
const gitee = readGiteeConfig(sources)
|
|
514
|
+
if (typeof gitee.clientSecret === 'string' && gitee.clientSecret !== '') secrets.gitee.clientSecret = gitee.clientSecret
|
|
515
|
+
if (typeof gitee.token === 'string' && gitee.token !== '') secrets.gitee.token = gitee.token
|
|
516
|
+
const cleanGitee = { ...(sources.gitee ?? {}), clientSecret: undefined, token: undefined }
|
|
517
|
+
const main = { ...sources, searchSources: cleanSearch, gitee: cleanGitee }
|
|
518
|
+
await writeFile(SOURCES_FILE, JSON.stringify(main, null, 2) + '\n', 'utf8')
|
|
519
|
+
await writeSourceSecrets(secrets)
|
|
471
520
|
}
|
|
472
521
|
|
|
473
522
|
/**
|
|
@@ -900,6 +949,25 @@ function isLoopback(address) {
|
|
|
900
949
|
return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'
|
|
901
950
|
}
|
|
902
951
|
|
|
952
|
+
/** 写请求跨站防护:浏览器同源策略不阻止跨站发送,必须校验 Origin / Sec-Fetch-Site。
|
|
953
|
+
* 非浏览器客户端(无 Origin/Sec-Fetch-Site 头)仍允许,避免 curl/脚本/测试被误伤。 */
|
|
954
|
+
function isAllowedWriteOrigin(req, port) {
|
|
955
|
+
const origin = typeof req.headers?.origin === 'string' ? req.headers.origin : null
|
|
956
|
+
const site = typeof req.headers?.['sec-fetch-site'] === 'string' ? req.headers['sec-fetch-site'] : null
|
|
957
|
+
if (origin !== null && origin !== '' && !ALLOWED_LOCAL_ORIGINS(port).has(origin)) return false
|
|
958
|
+
if (site !== null && site !== 'same-origin' && site !== 'none') return false
|
|
959
|
+
return true
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function ALLOWED_LOCAL_ORIGINS(port) {
|
|
963
|
+
return new Set([
|
|
964
|
+
`http://127.0.0.1:${port}`,
|
|
965
|
+
`http://localhost:${port}`,
|
|
966
|
+
`http://[::1]:${port}`,
|
|
967
|
+
])
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
|
|
903
971
|
function sendJson(res, status, body) {
|
|
904
972
|
const payload = JSON.stringify(body)
|
|
905
973
|
res.writeHead(status, {
|
|
@@ -939,7 +1007,6 @@ function githubRequestOnce(url, { signal, accept, token, timeout = 20000 } = {})
|
|
|
939
1007
|
accept: accept ?? 'application/vnd.github+json',
|
|
940
1008
|
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
941
1009
|
},
|
|
942
|
-
rejectUnauthorized: false,
|
|
943
1010
|
}, resolve)
|
|
944
1011
|
req.on('error', reject)
|
|
945
1012
|
req.setTimeout(timeout, () => req.destroy(new Error('GitHub 请求超时')))
|
|
@@ -1124,7 +1191,7 @@ async function githubText(url, signal, token = null) {
|
|
|
1124
1191
|
/** 用 curl 子进程请求 JSON(绕过本机中间设备对 node:https TLS 指纹的拦截;curl 走系统网络栈)。 */
|
|
1125
1192
|
async function curlJson(url, timeout = 15000, headers = {}) {
|
|
1126
1193
|
const bin = process.platform === 'win32' ? 'curl.exe' : 'curl'
|
|
1127
|
-
const args = ['-s', '-m', String(Math.max(5, Math.ceil(timeout / 1000))), '
|
|
1194
|
+
const args = ['-s', '-m', String(Math.max(5, Math.ceil(timeout / 1000))), '-H', 'accept: application/json', '-w', '\n__HTTP__%{http_code}']
|
|
1128
1195
|
for (const [key, value] of Object.entries(headers)) args.push('-H', `${key}: ${value}`)
|
|
1129
1196
|
args.push(url)
|
|
1130
1197
|
const { stdout } = await execFileAsync(bin, args, { timeout: timeout + 3000, windowsHide: true, maxBuffer: 8 * 1024 * 1024 })
|
|
@@ -1140,7 +1207,7 @@ async function curlText(url, timeout = 10000) {
|
|
|
1140
1207
|
const bin = process.platform === 'win32' ? 'curl.exe' : 'curl'
|
|
1141
1208
|
const { stdout } = await execFileAsync(
|
|
1142
1209
|
bin,
|
|
1143
|
-
['-s', '-m', String(Math.max(3, Math.ceil(timeout / 1000))), '
|
|
1210
|
+
['-s', '-m', String(Math.max(3, Math.ceil(timeout / 1000))), '-H', 'accept: text/plain', '-w', '\n__HTTP__%{http_code}', url],
|
|
1144
1211
|
{ timeout: timeout + 3000, windowsHide: true, maxBuffer: 8 * 1024 * 1024 },
|
|
1145
1212
|
)
|
|
1146
1213
|
const m = String(stdout).match(/\n__HTTP__(\d+)\s*$/u)
|
|
@@ -2559,6 +2626,18 @@ export function apply(ctx) {
|
|
|
2559
2626
|
sendError(res, 403, '仅允许本机访问')
|
|
2560
2627
|
return
|
|
2561
2628
|
}
|
|
2629
|
+
const port = webPort(ctx)
|
|
2630
|
+
const host = typeof req.headers?.host === 'string' ? req.headers.host : ''
|
|
2631
|
+
if (![`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`].includes(host)) {
|
|
2632
|
+
sendError(res, 403, 'Host 校验失败(仅允许本机访问)')
|
|
2633
|
+
return
|
|
2634
|
+
}
|
|
2635
|
+
// 安全护栏(issue #9):写路由必须是本机同源请求,防止恶意网页跨站驱动敏感操作。
|
|
2636
|
+
const method = req.method ?? 'GET'
|
|
2637
|
+
if (method !== 'GET' && method !== 'HEAD' && !isAllowedWriteOrigin(req, port)) {
|
|
2638
|
+
sendError(res, 403, '跨站请求被拒绝(Origin/Sec-Fetch-Site 校验)')
|
|
2639
|
+
return
|
|
2640
|
+
}
|
|
2562
2641
|
try {
|
|
2563
2642
|
await handle(ctx, req, res)
|
|
2564
2643
|
} catch (error) {
|
|
@@ -3445,18 +3524,18 @@ async function handle(ctx, req, res) {
|
|
|
3445
3524
|
`Set-Location -Path ${ps(dirname(dshDir))}`,
|
|
3446
3525
|
// npm 安装函数:Start-Process 跑 npm-cli.js(唯一正确入口——lib/cli.js 是函数模块,
|
|
3447
3526
|
// 直跑只加载不执行,假成功 exit 0)+ --force(显式指定版本但已在 package.json 的
|
|
3448
|
-
// semver 范围内时 npm 会跳过安装,假成功)+
|
|
3527
|
+
// semver 范围内时 npm 会跳过安装,假成功)+ 15 分钟总时长硬超时。注意:**不能加
|
|
3449
3528
|
// -RedirectStandardOutput/-RedirectStandardError**——schtasks 任务环境下带重定向的
|
|
3450
3529
|
// Start-Process 子进程会启动即异常退出(ExitCode 读不到、输出 0 字节,实测);
|
|
3451
3530
|
// 无重定向时 ExitCode 正常(实测 exit=0)。npm 自身 debug 日志(cache\_logs)留档。
|
|
3452
|
-
// 超时策略:总时长
|
|
3453
|
-
// 快速失败换 registry,不用干等
|
|
3531
|
+
// 超时策略:总时长 15 分钟硬上限 + npm debug 日志 5 分钟无更新判定卡死(网络黑洞/挂起时
|
|
3532
|
+
// 快速失败换 registry,不用干等 15 分钟);等待期间每 15 秒更新进度状态(客户端可见)。
|
|
3454
3533
|
// 成功后校验 package.json 版本 === target(防 npm 假成功)。
|
|
3455
3534
|
// 另注意:npm install 在 schtasks 环境启动慢(约 1-2 分钟 0 日志,初始化/编译缓存),
|
|
3456
3535
|
// 属正常,5 分钟卡死阈值不会误杀。
|
|
3457
3536
|
`function Install-Framework($reg) {`,
|
|
3458
3537
|
` $startAt = Get-Date`,
|
|
3459
|
-
` $deadline = $startAt.AddMinutes(
|
|
3538
|
+
` $deadline = $startAt.AddMinutes(15)`,
|
|
3460
3539
|
` $stallDeadline = $startAt.AddMinutes(5)`,
|
|
3461
3540
|
` $lastLogM = (Get-Date)`,
|
|
3462
3541
|
` $logSeen = $false`,
|
|
@@ -3493,7 +3572,7 @@ async function handle(ctx, req, res) {
|
|
|
3493
3572
|
` }`,
|
|
3494
3573
|
` }`,
|
|
3495
3574
|
` if (-not $done) {`,
|
|
3496
|
-
` Log 'pnpm 超过
|
|
3575
|
+
` Log 'pnpm 超过 15 分钟未完成,判定超时'`,
|
|
3497
3576
|
` try { taskkill /F /FI "WINDOWTITLE eq DSH-Upgrade" 2>$null | Out-Null } catch {}`,
|
|
3498
3577
|
` return 1`,
|
|
3499
3578
|
` }`,
|
|
@@ -3609,10 +3688,10 @@ async function handle(ctx, req, res) {
|
|
|
3609
3688
|
` $tgz = Join-Path $tmpDir 'pkg.tgz'`,
|
|
3610
3689
|
` $encName = $name.Replace('/','%2f')`,
|
|
3611
3690
|
` $url = 'https://registry.npmmirror.com/' + $encName + '/-/' + $short + '-' + $refVersion + '.tgz'`,
|
|
3612
|
-
` curl.exe -s -L -m 60
|
|
3691
|
+
` curl.exe -s -L -m 60 -o $tgz $url`,
|
|
3613
3692
|
` if (-not (Test-Path $tgz) -or (Get-Item $tgz).Length -lt 1000) {`,
|
|
3614
3693
|
` $url = 'https://registry.npmjs.org/' + $encName + '/-/' + $short + '-' + $refVersion + '.tgz'`,
|
|
3615
|
-
` curl.exe -s -L -m 60
|
|
3694
|
+
` curl.exe -s -L -m 60 -o $tgz $url`,
|
|
3616
3695
|
` }`,
|
|
3617
3696
|
` if (-not (Test-Path $tgz) -or (Get-Item $tgz).Length -lt 1000) { Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue; continue }`,
|
|
3618
3697
|
` tar -xzf $tgz -C $tmpDir`,
|
package/package.json
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@noob-stupid/dsh-plugin-console",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "DSH plugin management panel & marketplace: one-click enable/disable, multi-source market (GitHub/Gitee/custom), static-index market (500+ plugins / 300 skills), skills, suites, and one-click framework upgrade.",
|
|
5
|
-
"repository": {
|
|
6
|
-
"type": "git",
|
|
7
|
-
"url": "https://github.com/Noob-stupid/dsh-plugin-hub"
|
|
8
|
-
},
|
|
9
|
-
"type": "module",
|
|
10
|
-
"main": "./lib/index.js",
|
|
11
|
-
"exports": {
|
|
12
|
-
".": "./lib/index.js",
|
|
13
|
-
"./client": "./lib/client.js",
|
|
14
|
-
"./package.json": "./package.json"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"dsh-plugin",
|
|
18
|
-
"deepseek-harness",
|
|
19
|
-
"plugin-manager"
|
|
20
|
-
],
|
|
21
|
-
"dsh": {
|
|
22
|
-
"bundle": {
|
|
23
|
-
"patch": "./cordis.patch.yml"
|
|
24
|
-
},
|
|
25
|
-
"client": {
|
|
26
|
-
"platform": "web",
|
|
27
|
-
"inject": [
|
|
28
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
29
|
-
"@deepseek-ai/dsh-client-locale",
|
|
30
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
"files": [
|
|
35
|
-
"lib",
|
|
36
|
-
"cordis.patch.yml"
|
|
37
|
-
],
|
|
38
|
-
"license": "MIT"
|
|
39
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@noob-stupid/dsh-plugin-console",
|
|
3
|
+
"version": "0.3.18",
|
|
4
|
+
"description": "DSH plugin management panel & marketplace: one-click enable/disable, multi-source market (GitHub/Gitee/custom), static-index market (500+ plugins / 300 skills), skills, suites, and one-click framework upgrade.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/Noob-stupid/dsh-plugin-hub"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./lib/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./lib/index.js",
|
|
13
|
+
"./client": "./lib/client.js",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"dsh-plugin",
|
|
18
|
+
"deepseek-harness",
|
|
19
|
+
"plugin-manager"
|
|
20
|
+
],
|
|
21
|
+
"dsh": {
|
|
22
|
+
"bundle": {
|
|
23
|
+
"patch": "./cordis.patch.yml"
|
|
24
|
+
},
|
|
25
|
+
"client": {
|
|
26
|
+
"platform": "web",
|
|
27
|
+
"inject": [
|
|
28
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
29
|
+
"@deepseek-ai/dsh-client-locale",
|
|
30
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"lib",
|
|
36
|
+
"cordis.patch.yml"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT"
|
|
39
|
+
}
|