@noob-stupid/dsh-plugin-console 0.3.9 → 0.3.12
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 +7 -3
- package/README.zh.md +7 -3
- package/lib/client.js +12 -4
- package/lib/index.js +119 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,14 +70,18 @@ static plugin & skill index** refreshed by CI every 6 hours.
|
|
|
70
70
|
The plugin declares a `dsh.bundle` manifest, so a single command installs and enables it:
|
|
71
71
|
|
|
72
72
|
```sh
|
|
73
|
+
# npm release (recommended: prebuilt, no git / build authorization needed)
|
|
74
|
+
dsh plugin --profile web add @noob-stupid/dsh-plugin-console
|
|
75
|
+
|
|
76
|
+
# or install from GitHub source (needs git; allowBuilds authorization on first add)
|
|
73
77
|
dsh plugin --profile web add github:Noob-stupid/dsh-plugin-hub
|
|
74
78
|
```
|
|
75
79
|
|
|
76
80
|
Uninstall / reinstall (update):
|
|
77
81
|
|
|
78
82
|
```sh
|
|
79
|
-
dsh plugin --profile web remove
|
|
80
|
-
dsh plugin --profile web add
|
|
83
|
+
dsh plugin --profile web remove @noob-stupid/dsh-plugin-console
|
|
84
|
+
dsh plugin --profile web add @noob-stupid/dsh-plugin-console
|
|
81
85
|
```
|
|
82
86
|
|
|
83
87
|
Then restart the dsh service → refresh the page → Settings → Plugins → Plugin Console.
|
|
@@ -105,7 +109,7 @@ idempotently appends an enable entry to `cordis.patch.yml`. Afterwards:
|
|
|
105
109
|
|
|
106
110
|
### Option 3: hand it to an AI in one sentence
|
|
107
111
|
|
|
108
|
-
> Install the DSH plugin hub (dsh-plugin-hub): run `dsh plugin --profile web add
|
|
112
|
+
> Install the DSH plugin hub (dsh-plugin-hub): run `dsh plugin --profile web add @noob-stupid/dsh-plugin-console` (npm release); if there is no dsh CLI, clone https://github.com/Noob-stupid/dsh-plugin-hub to `~/.dsh/profiles/web/node_modules/` and register it in `cordis.patch.yml` (id: plugin-console, name: @noob-stupid/dsh-plugin-console). Restart dsh web afterwards.
|
|
109
113
|
|
|
110
114
|
Requires: DSH ≥ 0.1.0-rc.6 (web profile, with `dsh-client-modules` / `dsh-host-plugin-inventory`).
|
|
111
115
|
|
package/README.zh.md
CHANGED
|
@@ -64,14 +64,18 @@
|
|
|
64
64
|
插件声明了 `dsh.bundle` 官方清单,一条命令装好并自动启用:
|
|
65
65
|
|
|
66
66
|
```sh
|
|
67
|
+
# npm 发布版(推荐:预构建产物,无需 git / 构建授权)
|
|
68
|
+
dsh plugin --profile web add @noob-stupid/dsh-plugin-console
|
|
69
|
+
|
|
70
|
+
# 或直接装 GitHub 源码(需 git,首次需 allowBuilds 授权)
|
|
67
71
|
dsh plugin --profile web add github:Noob-stupid/dsh-plugin-hub
|
|
68
72
|
```
|
|
69
73
|
|
|
70
74
|
卸载 / 重装(即更新):
|
|
71
75
|
|
|
72
76
|
```sh
|
|
73
|
-
dsh plugin --profile web remove
|
|
74
|
-
dsh plugin --profile web add
|
|
77
|
+
dsh plugin --profile web remove @noob-stupid/dsh-plugin-console
|
|
78
|
+
dsh plugin --profile web add @noob-stupid/dsh-plugin-console
|
|
75
79
|
```
|
|
76
80
|
|
|
77
81
|
然后重启 dsh 服务 → 刷新页面 → 设置 → 插件 → 插件管理。
|
|
@@ -98,7 +102,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
|
|
|
98
102
|
|
|
99
103
|
### 方式三:一句话交给 AI
|
|
100
104
|
|
|
101
|
-
> 安装 DSH 插件中心(dsh-plugin-hub):运行 `dsh plugin --profile web add
|
|
105
|
+
> 安装 DSH 插件中心(dsh-plugin-hub):运行 `dsh plugin --profile web add @noob-stupid/dsh-plugin-console`(npm 发布版);若没有 dsh CLI,则克隆 https://github.com/Noob-stupid/dsh-plugin-hub 到 `~/.dsh/profiles/web/node_modules/`,在 `cordis.patch.yml` 注册(id: plugin-console,name: @noob-stupid/dsh-plugin-console)。完成后重启 dsh web。
|
|
102
106
|
|
|
103
107
|
要求:DSH ≥ 0.1.0-rc.6(web profile,含 `dsh-client-modules` / `dsh-host-plugin-inventory`)。
|
|
104
108
|
|
package/lib/client.js
CHANGED
|
@@ -947,7 +947,7 @@ window.__ModuleLoader__.load({
|
|
|
947
947
|
const [giteeClientSecret, setGiteeClientSecret] = react.useState("");
|
|
948
948
|
// 派生:Gitee 登录状态
|
|
949
949
|
const giteeStatus = sourcesData !== null
|
|
950
|
-
? { clientConfigured: !!sourcesData.gitee?.
|
|
950
|
+
? { clientConfigured: !!sourcesData.gitee?.clientConfigured, hasToken: !!sourcesData.gitee?.hasToken, login: sourcesData.gitee?.login ?? "" }
|
|
951
951
|
: null;
|
|
952
952
|
// 搜索源(默认 GitHub;点击登录标切换),localStorage 持久化
|
|
953
953
|
const [searchSource, setSearchSource] = react.useState(() => {
|
|
@@ -1487,17 +1487,25 @@ window.__ModuleLoader__.load({
|
|
|
1487
1487
|
setSourcesData(data.sources);
|
|
1488
1488
|
setSearchSourcesList(data.sources.searchSources ?? []);
|
|
1489
1489
|
setGiteeClientId(data.sources.gitee?.clientId ?? "");
|
|
1490
|
-
|
|
1490
|
+
// 安全:secret 永不回传(服务端已脱敏);已配置时留空、保存需重新输入,
|
|
1491
|
+
// 未配置时为空表单引导首次填写
|
|
1492
|
+
setGiteeClientSecret("");
|
|
1491
1493
|
},
|
|
1492
1494
|
(error) => { setSourcesOpen(false); setMessage(t("failed") + ":" + friendlyGithubError(error).message); },
|
|
1493
1495
|
);
|
|
1494
1496
|
};
|
|
1495
1497
|
const saveGiteeSetup = () => {
|
|
1496
|
-
|
|
1498
|
+
// clientId 打码回显(如 abc12345…):用户未修改时原样传回服务端,服务端识别打码标记保留原配置
|
|
1499
|
+
const idValue = giteeClientId.trim();
|
|
1500
|
+
if (!idValue || !giteeClientSecret.trim()) {
|
|
1497
1501
|
setMessage(t("failed") + ":client_id / client_secret 不能为空");
|
|
1498
1502
|
return;
|
|
1499
1503
|
}
|
|
1500
|
-
|
|
1504
|
+
if (idValue.endsWith("…")) {
|
|
1505
|
+
sourcesAction({ action: "gitee-setup", clientId: "", clientSecret: giteeClientSecret.trim(), keepClientId: true });
|
|
1506
|
+
} else {
|
|
1507
|
+
sourcesAction({ action: "gitee-setup", clientId: idValue, clientSecret: giteeClientSecret.trim() });
|
|
1508
|
+
}
|
|
1501
1509
|
};
|
|
1502
1510
|
const startGiteeOauth = () => {
|
|
1503
1511
|
call("/plugin-console/gitee-oauth-url").then(
|
package/lib/index.js
CHANGED
|
@@ -52,6 +52,7 @@ const PROTECTED_MODULE_PATTERNS = [
|
|
|
52
52
|
/^@deepseek-ai\/dsh-client-hmr$/u,
|
|
53
53
|
/^@deepseek-ai\/dsh-client-runtime$/u,
|
|
54
54
|
/^@deepseek-ai\/dsh-client-locale$/u,
|
|
55
|
+
/^@deepseek-ai\/dsh-client-ui-attachment$/u,
|
|
55
56
|
/^@deepseek-ai\/dsh-client-web/u,
|
|
56
57
|
/^@deepseek-ai\/dsh-web-frontend$/u,
|
|
57
58
|
/^@deepseek-ai\/dsh-web-app$/u,
|
|
@@ -59,6 +60,7 @@ const PROTECTED_MODULE_PATTERNS = [
|
|
|
59
60
|
/^@deepseek-ai\/dsh-credentials/u,
|
|
60
61
|
/^@deepseek-ai\/dsh-session/u,
|
|
61
62
|
/^@deepseek-ai\/dsh-storage/u,
|
|
63
|
+
/^@deepseek-ai\/dsh-attachment/u,
|
|
62
64
|
/^@deepseek-ai\/dsh-typert/u,
|
|
63
65
|
/^@deepseek-ai\/dsh-api-remotes$/u,
|
|
64
66
|
/^@deepseek-ai\/dsh-tools$/u,
|
|
@@ -305,7 +307,7 @@ async function removeInsertRow(patchPath, rowId) {
|
|
|
305
307
|
let next = text.replace(blockRe, '')
|
|
306
308
|
const overrideRe = new RegExp(`^- id: ${escapeRegExp(rowId)}\\s*\\r?\\n {2}disabled: (true|false)\\s*\\r?\\n`, 'mu')
|
|
307
309
|
next = next.replace(overrideRe, '')
|
|
308
|
-
if (next !== text) await writeFile(patchPath, next, 'utf8')
|
|
310
|
+
if (next !== text) await writeFile(patchPath, sanitizePatchText(next), 'utf8')
|
|
309
311
|
})
|
|
310
312
|
}
|
|
311
313
|
|
|
@@ -468,6 +470,42 @@ async function writeSources(sources) {
|
|
|
468
470
|
await writeFile(SOURCES_FILE, JSON.stringify(sources, null, 2) + '\n', 'utf8')
|
|
469
471
|
}
|
|
470
472
|
|
|
473
|
+
/**
|
|
474
|
+
* 凭据脱敏(安全审查发现):/sources 响应不得携带明文密钥——
|
|
475
|
+
* - Gitee clientSecret / token:绝不回传(clientId 打码保留前 8 位供识别)
|
|
476
|
+
* - 自定义搜索源的 headers(可能含 Authorization: Bearer xxx):value 打码
|
|
477
|
+
* 前端需要"已配置"状态时用 giteeStatusView 的布尔字段。
|
|
478
|
+
*/
|
|
479
|
+
function maskSources(sources) {
|
|
480
|
+
const gitee = readGiteeConfig(sources)
|
|
481
|
+
const maskedGitee = {
|
|
482
|
+
clientId: gitee.clientId === '' ? '' : `${gitee.clientId.slice(0, 8)}…`,
|
|
483
|
+
clientConfigured: gitee.clientId !== '',
|
|
484
|
+
hasToken: gitee.token !== '',
|
|
485
|
+
login: gitee.login,
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
registries: sources.registries,
|
|
489
|
+
searchSources: (sources.searchSources ?? []).map((s) => {
|
|
490
|
+
if (s && typeof s.headers === 'object' && Object.keys(s.headers).length > 0) {
|
|
491
|
+
const masked = {}
|
|
492
|
+
for (const [k, v] of Object.entries(s.headers)) {
|
|
493
|
+
masked[k] = typeof v === 'string' && v.length > 8 ? `${v.slice(0, 4)}…${v.slice(-4)}` : (v === '' ? '' : '••••')
|
|
494
|
+
}
|
|
495
|
+
return { ...s, headers: masked }
|
|
496
|
+
}
|
|
497
|
+
return s
|
|
498
|
+
}),
|
|
499
|
+
gitee: maskedGitee,
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Gitee 配置状态视图(布尔 + login,无任何凭据)。 */
|
|
504
|
+
function giteeStatusView(sources) {
|
|
505
|
+
const gitee = readGiteeConfig(sources)
|
|
506
|
+
return { clientConfigured: gitee.clientId !== '', hasToken: gitee.token !== '', login: gitee.login }
|
|
507
|
+
}
|
|
508
|
+
|
|
471
509
|
/** 按主→备顺序返回 registry URL 列表。 */
|
|
472
510
|
function orderedRegistries(sources) {
|
|
473
511
|
const list = [...sources.registries]
|
|
@@ -513,7 +551,8 @@ function readGithubAuth() {
|
|
|
513
551
|
* 因此面板披露版本并给出提示,而不是默默失效。
|
|
514
552
|
*/
|
|
515
553
|
const CONSOLE_VERSION = '0.1.0'
|
|
516
|
-
|
|
554
|
+
// 支持 0.1.x 系列(0.1.0 / 0.1.1 等);0.2 / 1.0 等破坏性大版本才标记不支持
|
|
555
|
+
const SUPPORTED_WEB_APP_PATTERN = /^0\.1\.\d+/u
|
|
517
556
|
|
|
518
557
|
async function detectCompat(baseUrl) {
|
|
519
558
|
const result = { consoleVersion: CONSOLE_VERSION, webAppVersion: null, dshVersion: null, supported: true, notice: null }
|
|
@@ -539,7 +578,7 @@ async function detectCompat(baseUrl) {
|
|
|
539
578
|
}
|
|
540
579
|
if (result.webAppVersion !== null && !SUPPORTED_WEB_APP_PATTERN.test(result.webAppVersion)) {
|
|
541
580
|
result.supported = false
|
|
542
|
-
result.notice = `当前 DSH web 包版本 ${result.webAppVersion} 不在受支持的 0.1.
|
|
581
|
+
result.notice = `当前 DSH web 包版本 ${result.webAppVersion} 不在受支持的 0.1.x 系列内,插件控制台的部分功能可能因官方破坏性更新而失效;请到 https://github.com/Noob-stupid/dsh-plugin-hub 获取匹配的更新`
|
|
543
582
|
}
|
|
544
583
|
return result
|
|
545
584
|
}
|
|
@@ -758,12 +797,29 @@ function disableBlock(id) {
|
|
|
758
797
|
return `- id: ${id}\n disabled: true\n`
|
|
759
798
|
}
|
|
760
799
|
|
|
800
|
+
/**
|
|
801
|
+
* 清理补丁文件中的顶层空数组占位符(issue #7 事故教训):
|
|
802
|
+
* DSH profile 模板的 cordis.patch.yml 以注释 + 顶层 `[]` 占位符初始化(如 `# ...\n[]`)。
|
|
803
|
+
* 直接追加条目会生成 `[]` 后又跟 `- id: xxx` 的非法 YAML(同文档流里数组结束符 + 后续项),
|
|
804
|
+
* 导致 dsh 启动解析崩溃。写入前必须移除顶层独立的 `[]` / `[ ]` 占位行。
|
|
805
|
+
* 仅处理"整行就是空数组"的占位符;合法内容(如 `- insert:` 列表)不受影响。
|
|
806
|
+
*/
|
|
807
|
+
function sanitizePatchText(text) {
|
|
808
|
+
return text
|
|
809
|
+
.split(/\r?\n/u)
|
|
810
|
+
.filter((line) => !/^\s*\[\s*\]\s*$/u.test(line))
|
|
811
|
+
.join('\n')
|
|
812
|
+
.replace(/\n{3,}/gu, '\n\n')
|
|
813
|
+
.replace(/\s+$/u, '') + '\n'
|
|
814
|
+
}
|
|
815
|
+
|
|
761
816
|
/** 停用:追加 disabled:true 块(已存在则不动)。 */
|
|
762
817
|
async function disableEntry(patchPath, id) {
|
|
763
818
|
return queuedWrite(async () => {
|
|
764
819
|
const { disables, text } = await readPatchState(patchPath)
|
|
765
820
|
if (disables.includes(id)) return { changed: false }
|
|
766
|
-
const
|
|
821
|
+
const clean = sanitizePatchText(text)
|
|
822
|
+
const next = clean.length === 0 || clean.endsWith('\n') ? clean : `${clean}\n`
|
|
767
823
|
await writeFile(patchPath, `${next}${disableBlock(id)}`, 'utf8')
|
|
768
824
|
return { changed: true }
|
|
769
825
|
})
|
|
@@ -775,11 +831,12 @@ async function enableEntry(patchPath, id) {
|
|
|
775
831
|
const { disables, forced, text } = await readPatchState(patchPath)
|
|
776
832
|
const blockRe = new RegExp(`^- id: ${escapeRegExp(id)}\\r?\\n disabled: true\\r?\\n`, 'mu')
|
|
777
833
|
if (blockRe.test(text)) {
|
|
778
|
-
await writeFile(patchPath, text.replace(blockRe, ''), 'utf8')
|
|
834
|
+
await writeFile(patchPath, sanitizePatchText(text.replace(blockRe, '')), 'utf8')
|
|
779
835
|
return { changed: true }
|
|
780
836
|
}
|
|
781
837
|
if (forced.includes(id)) return { changed: false }
|
|
782
|
-
const
|
|
838
|
+
const clean = sanitizePatchText(text)
|
|
839
|
+
const next = clean.length === 0 || clean.endsWith('\n') ? clean : `${clean}\n`
|
|
783
840
|
await writeFile(patchPath, `${next}- id: ${id}\n disabled: false\n`, 'utf8')
|
|
784
841
|
return { changed: true }
|
|
785
842
|
})
|
|
@@ -790,7 +847,8 @@ async function appendInsert(patchPath, entryId, packageName) {
|
|
|
790
847
|
return queuedWrite(async () => {
|
|
791
848
|
const { inserts, text } = await readPatchState(patchPath)
|
|
792
849
|
if (inserts.includes(entryId)) return { changed: false }
|
|
793
|
-
const
|
|
850
|
+
const clean = sanitizePatchText(text)
|
|
851
|
+
const next = clean.length === 0 || clean.endsWith('\n') ? clean : `${clean}\n`
|
|
794
852
|
const block = `- insert:\n - id: ${entryId}\n name: '${packageName}'\n`
|
|
795
853
|
await writeFile(patchPath, `${next}${block}`, 'utf8')
|
|
796
854
|
return { changed: true }
|
|
@@ -1241,31 +1299,44 @@ function githubRepoInfo(repo) {
|
|
|
1241
1299
|
}
|
|
1242
1300
|
|
|
1243
1301
|
/** raw 文件读取:https 主通道(含镜像)、gh CLI、curl 直连、curl jsDelivr CDN **四通道并行竞速**。
|
|
1244
|
-
* 黑洞期 https/ghproxy 全挂、gh 可能不在服务进程 PATH——jsDelivr CDN 走系统网络且国内可达,是最后兜底。
|
|
1302
|
+
* 黑洞期 https/ghproxy 全挂、gh 可能不在服务进程 PATH——jsDelivr CDN 走系统网络且国内可达,是最后兜底。
|
|
1303
|
+
* 关键语义:**404 是确定性结果**(文件不存在,探测 .gitmodules/SKILL.md 等时的正常答案),
|
|
1304
|
+
* 必须立即返回 null,不能等其它分支(黑洞期 https 通道 20s+ 才失败会拖死整个请求)。 */
|
|
1245
1305
|
async function rawTextWithFallback(repo, branch, path) {
|
|
1246
1306
|
const url = `${GITHUB_RAW}/${repo}/${encodeURIComponent(branch)}/${path}`
|
|
1247
|
-
|
|
1307
|
+
// 404/4xx:确定性"不存在",立即以 null 胜出(不等慢分支)
|
|
1308
|
+
const notFound = (promise) => promise
|
|
1309
|
+
.catch((error) => {
|
|
1310
|
+
if (error instanceof Error && /HTTP 404|HTTP 400|Not Found|not found|非 2xx|HTTP 非 2xx/u.test(error.message)) return null
|
|
1311
|
+
throw error
|
|
1312
|
+
})
|
|
1313
|
+
const https = notFound(githubText(url)
|
|
1248
1314
|
.then((body) => {
|
|
1249
1315
|
if (body === null) throw new Error('HTTP 非 2xx')
|
|
1250
1316
|
return body
|
|
1251
|
-
})
|
|
1252
|
-
const gh = githubViaGh(`repos/${repo}/contents/${path}?ref=${encodeURIComponent(branch)}`)
|
|
1317
|
+
}))
|
|
1318
|
+
const gh = notFound(githubViaGh(`repos/${repo}/contents/${path}?ref=${encodeURIComponent(branch)}`)
|
|
1253
1319
|
.then((data) => {
|
|
1254
1320
|
if (data && typeof data.content === 'string') return Buffer.from(data.content, 'base64').toString('utf8')
|
|
1255
1321
|
throw new Error('contents 无内容')
|
|
1256
|
-
})
|
|
1257
|
-
const curl = curlText(url,
|
|
1322
|
+
}))
|
|
1323
|
+
const curl = notFound(curlText(url, 6000)
|
|
1258
1324
|
.then((body) => {
|
|
1259
1325
|
if (body === null) throw new Error('HTTP 非 2xx')
|
|
1260
1326
|
return body
|
|
1261
|
-
})
|
|
1262
|
-
const cdn = curlText(`https://cdn.jsdelivr.net/gh/${repo}@${encodeURIComponent(branch)}/${path}`,
|
|
1327
|
+
}))
|
|
1328
|
+
const cdn = notFound(curlText(`https://cdn.jsdelivr.net/gh/${repo}@${encodeURIComponent(branch)}/${path}`, 6000)
|
|
1263
1329
|
.then((body) => {
|
|
1264
1330
|
if (body === null) throw new Error('HTTP 非 2xx')
|
|
1265
1331
|
return body
|
|
1266
|
-
})
|
|
1332
|
+
}))
|
|
1267
1333
|
try {
|
|
1268
|
-
|
|
1334
|
+
// 任一通道成功(含 404→null)立即返回;整体 5s 封顶(jsDelivr 正常 0.7s,
|
|
1335
|
+
// 黑洞期快速失败优于等 12s),超时视为不可达(null)。
|
|
1336
|
+
return await Promise.race([
|
|
1337
|
+
Promise.any([https, gh, curl, cdn]),
|
|
1338
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 5000)),
|
|
1339
|
+
])
|
|
1269
1340
|
} catch {
|
|
1270
1341
|
return null
|
|
1271
1342
|
}
|
|
@@ -1441,10 +1512,15 @@ function parseBundlePatchRefs(text) {
|
|
|
1441
1512
|
* 捆绑依赖补装(事故教训:dsh-web-ui-all 更新后 17 个捆绑依赖缺失 → 服务加载崩溃):
|
|
1442
1513
|
* curl/GitHub 通道只解压主包,这里逐个补装直接依赖(pnpm → curl 依次尝试)。
|
|
1443
1514
|
* 返回仍缺失的依赖列表。
|
|
1515
|
+
* 安全护栏(事故教训):@deepseek-ai/* 框架内部包**绝不补装**——它们由框架依赖树管理
|
|
1516
|
+
* (正确版本随 @deepseek-ai/dsh 一起安装),npm 上这些内部包的 dist-tags.latest 是远古
|
|
1517
|
+
* 版本(如 dsh-host-webserver@0.0.1-rc.1),无版本约束补装会覆盖框架正确版本,
|
|
1518
|
+
* 导致 webServer 等服务起不来、整个 profile 启动崩溃。
|
|
1444
1519
|
*/
|
|
1445
1520
|
async function backfillMissingDeps(profileDir, deps, registries) {
|
|
1446
1521
|
const stillMissing = []
|
|
1447
1522
|
for (const dep of deps) {
|
|
1523
|
+
if (/^@deepseek-ai\//u.test(dep)) continue // 框架内部包:跳过(宿主提供)
|
|
1448
1524
|
if (existsSync(join(profileDir, 'node_modules', dep))) continue
|
|
1449
1525
|
let ok = false
|
|
1450
1526
|
try {
|
|
@@ -1453,8 +1529,10 @@ async function backfillMissingDeps(profileDir, deps, registries) {
|
|
|
1453
1529
|
} catch {}
|
|
1454
1530
|
if (!ok) {
|
|
1455
1531
|
try {
|
|
1456
|
-
|
|
1457
|
-
|
|
1532
|
+
await curlManualInstall(profileDir, dep, registries)
|
|
1533
|
+
// 判断目标是否实际安装(修复:旧代码用 depInfo.missingDeps.length===0 判断
|
|
1534
|
+
// "该依赖自身无依赖",只要目标依赖带依赖就误报缺失——即使 curl 已成功安装)
|
|
1535
|
+
ok = existsSync(join(profileDir, 'node_modules', dep))
|
|
1458
1536
|
} catch {}
|
|
1459
1537
|
}
|
|
1460
1538
|
if (!ok) stillMissing.push(dep)
|
|
@@ -1503,7 +1581,8 @@ async function githubReleaseInstall(profileDir, repo, packageName) {
|
|
|
1503
1581
|
// 盒子实验:静态验证通过才覆盖(失败保留旧版本)。引用检查根用目标目录(宿主插件在根层)
|
|
1504
1582
|
const box = verifyPackageBox(pkgPath, profileDir, packageName, targetRoot)
|
|
1505
1583
|
const pkg = JSON.parse(readFileSync(join(pkgPath, 'package.json'), 'utf8'))
|
|
1506
|
-
|
|
1584
|
+
// 只统计 dependencies(peerDependencies 是宿主契约,不补装——见 curlManualInstall 注释)
|
|
1585
|
+
const deps = { ...(pkg.dependencies ?? {}) }
|
|
1507
1586
|
const missingDeps = Object.keys(deps).filter((d) => !existsSync(join(profileDir, 'node_modules', d)))
|
|
1508
1587
|
const target = join(targetRoot, packageName)
|
|
1509
1588
|
if (existsSync(target)) rmSync(target, { recursive: true, force: true })
|
|
@@ -1564,6 +1643,9 @@ async function ensureBundlePatchIntegrity(profileDir, pkgName, userPatchPath) {
|
|
|
1564
1643
|
if (refs.length === 0) return report
|
|
1565
1644
|
const registries = orderedRegistries(readSources())
|
|
1566
1645
|
for (const ref of refs) {
|
|
1646
|
+
// 框架内部包跳过(事故教训:npm 上 @deepseek-ai/* 的 dist-tags.latest 是远古版本,
|
|
1647
|
+
// 无版本约束补装会覆盖框架正确版本导致服务崩溃——见 backfillMissingDeps 注释)
|
|
1648
|
+
if (/^@deepseek-ai\//u.test(ref.name)) continue
|
|
1567
1649
|
if (existsSync(join(profileDir, 'node_modules', ref.name))) continue
|
|
1568
1650
|
let ok = false
|
|
1569
1651
|
try {
|
|
@@ -1581,7 +1663,10 @@ async function ensureBundlePatchIntegrity(profileDir, pkgName, userPatchPath) {
|
|
|
1581
1663
|
try {
|
|
1582
1664
|
const userPatch = readFileSync(userPatchPath, 'utf8')
|
|
1583
1665
|
if (!userPatch.includes(`id: ${ref.id}`)) {
|
|
1584
|
-
|
|
1666
|
+
// issue #7 防护:清理顶层 [] 占位符后再追加(模板初始文件直接追加会生成非法 YAML)
|
|
1667
|
+
const clean = sanitizePatchText(userPatch)
|
|
1668
|
+
const next = clean.length === 0 || clean.endsWith('\n') ? clean : `${clean}\n`
|
|
1669
|
+
writeFileSync(userPatchPath, `${next}- id: ${ref.id}\n disabled: true\n`, 'utf8')
|
|
1585
1670
|
report.disabled.push(ref.id)
|
|
1586
1671
|
}
|
|
1587
1672
|
} catch {}
|
|
@@ -2495,8 +2580,7 @@ async function handle(ctx, req, res) {
|
|
|
2495
2580
|
|
|
2496
2581
|
if (method === 'GET' && pathname === `${ROUTE_PREFIX}/sources`) {
|
|
2497
2582
|
const sources = readSources()
|
|
2498
|
-
|
|
2499
|
-
sendJson(res, 200, { ok: true, sources, giteeStatus: { clientConfigured: gitee.clientId !== '', hasToken: gitee.token !== '', login: gitee.login } })
|
|
2583
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources), giteeStatus: giteeStatusView(sources) })
|
|
2500
2584
|
return
|
|
2501
2585
|
}
|
|
2502
2586
|
|
|
@@ -2958,7 +3042,7 @@ async function handle(ctx, req, res) {
|
|
|
2958
3042
|
}
|
|
2959
3043
|
sources.registries.push(entry)
|
|
2960
3044
|
await writeSources(sources)
|
|
2961
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3045
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
2962
3046
|
return
|
|
2963
3047
|
}
|
|
2964
3048
|
if (action === 'remove') {
|
|
@@ -2985,7 +3069,7 @@ async function handle(ctx, req, res) {
|
|
|
2985
3069
|
target.url = url
|
|
2986
3070
|
target.name = name || url
|
|
2987
3071
|
await writeSources(sources)
|
|
2988
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3072
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
2989
3073
|
return
|
|
2990
3074
|
}
|
|
2991
3075
|
if (action === 'set-primary') {
|
|
@@ -2996,7 +3080,7 @@ async function handle(ctx, req, res) {
|
|
|
2996
3080
|
}
|
|
2997
3081
|
for (const r of sources.registries) r.primary = r.id === id
|
|
2998
3082
|
await writeSources(sources)
|
|
2999
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3083
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3000
3084
|
return
|
|
3001
3085
|
}
|
|
3002
3086
|
if (action === 'add-search') {
|
|
@@ -3028,7 +3112,7 @@ async function handle(ctx, req, res) {
|
|
|
3028
3112
|
...(headers.length > 0 ? { headers } : {}),
|
|
3029
3113
|
})
|
|
3030
3114
|
await writeSources(sources)
|
|
3031
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3115
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3032
3116
|
return
|
|
3033
3117
|
}
|
|
3034
3118
|
if (action === 'remove-search') {
|
|
@@ -3044,33 +3128,35 @@ async function handle(ctx, req, res) {
|
|
|
3044
3128
|
}
|
|
3045
3129
|
sources.searchSources = sources.searchSources.filter((s) => s.id !== id)
|
|
3046
3130
|
await writeSources(sources)
|
|
3047
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3131
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3048
3132
|
return
|
|
3049
3133
|
}
|
|
3050
3134
|
if (action === 'reset') {
|
|
3051
3135
|
const defaults = JSON.parse(JSON.stringify(DEFAULT_SOURCES))
|
|
3052
3136
|
await writeSources(defaults)
|
|
3053
|
-
sendJson(res, 200, { ok: true, sources: defaults })
|
|
3137
|
+
sendJson(res, 200, { ok: true, sources: maskSources(defaults) })
|
|
3054
3138
|
return
|
|
3055
3139
|
}
|
|
3056
3140
|
if (action === 'gitee-setup') {
|
|
3057
3141
|
const clientId = typeof body.clientId === 'string' ? body.clientId.trim() : ''
|
|
3058
3142
|
const clientSecret = typeof body.clientSecret === 'string' ? body.clientSecret.trim() : ''
|
|
3059
|
-
|
|
3143
|
+
const keepClientId = body.keepClientId === true
|
|
3144
|
+
if ((!keepClientId && !clientId) || !clientSecret) {
|
|
3060
3145
|
sendError(res, 400, 'client_id 与 client_secret 不能为空')
|
|
3061
3146
|
return
|
|
3062
3147
|
}
|
|
3063
3148
|
const current = readGiteeConfig(sources)
|
|
3064
|
-
|
|
3149
|
+
// keepClientId:前端回显的是打码 clientId(abc12345…),用户只改了 secret 时保留原配置
|
|
3150
|
+
sources.gitee = { ...current, clientId: keepClientId ? current.clientId : clientId, clientSecret }
|
|
3065
3151
|
await writeSources(sources)
|
|
3066
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3152
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3067
3153
|
return
|
|
3068
3154
|
}
|
|
3069
3155
|
if (action === 'gitee-clear') {
|
|
3070
3156
|
const current = readGiteeConfig(sources)
|
|
3071
3157
|
sources.gitee = { clientId: current.clientId, clientSecret: current.clientSecret, token: '', login: '' }
|
|
3072
3158
|
await writeSources(sources)
|
|
3073
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3159
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3074
3160
|
return
|
|
3075
3161
|
}
|
|
3076
3162
|
sendError(res, 400, '未知操作(add / edit / set-primary / remove / add-search / remove-search / gitee-setup / gitee-clear / reset)')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noob-stupid/dsh-plugin-console",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
4
4
|
"description": "DSH plugin management panel \u0026 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
5
|
"repository": {
|
|
6
6
|
"type": "git",
|