@noob-stupid/dsh-plugin-console 0.3.9 → 0.3.11
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 +116 -31
- 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]
|
|
@@ -758,12 +796,29 @@ function disableBlock(id) {
|
|
|
758
796
|
return `- id: ${id}\n disabled: true\n`
|
|
759
797
|
}
|
|
760
798
|
|
|
799
|
+
/**
|
|
800
|
+
* 清理补丁文件中的顶层空数组占位符(issue #7 事故教训):
|
|
801
|
+
* DSH profile 模板的 cordis.patch.yml 以注释 + 顶层 `[]` 占位符初始化(如 `# ...\n[]`)。
|
|
802
|
+
* 直接追加条目会生成 `[]` 后又跟 `- id: xxx` 的非法 YAML(同文档流里数组结束符 + 后续项),
|
|
803
|
+
* 导致 dsh 启动解析崩溃。写入前必须移除顶层独立的 `[]` / `[ ]` 占位行。
|
|
804
|
+
* 仅处理"整行就是空数组"的占位符;合法内容(如 `- insert:` 列表)不受影响。
|
|
805
|
+
*/
|
|
806
|
+
function sanitizePatchText(text) {
|
|
807
|
+
return text
|
|
808
|
+
.split(/\r?\n/u)
|
|
809
|
+
.filter((line) => !/^\s*\[\s*\]\s*$/u.test(line))
|
|
810
|
+
.join('\n')
|
|
811
|
+
.replace(/\n{3,}/gu, '\n\n')
|
|
812
|
+
.replace(/\s+$/u, '') + '\n'
|
|
813
|
+
}
|
|
814
|
+
|
|
761
815
|
/** 停用:追加 disabled:true 块(已存在则不动)。 */
|
|
762
816
|
async function disableEntry(patchPath, id) {
|
|
763
817
|
return queuedWrite(async () => {
|
|
764
818
|
const { disables, text } = await readPatchState(patchPath)
|
|
765
819
|
if (disables.includes(id)) return { changed: false }
|
|
766
|
-
const
|
|
820
|
+
const clean = sanitizePatchText(text)
|
|
821
|
+
const next = clean.length === 0 || clean.endsWith('\n') ? clean : `${clean}\n`
|
|
767
822
|
await writeFile(patchPath, `${next}${disableBlock(id)}`, 'utf8')
|
|
768
823
|
return { changed: true }
|
|
769
824
|
})
|
|
@@ -775,11 +830,12 @@ async function enableEntry(patchPath, id) {
|
|
|
775
830
|
const { disables, forced, text } = await readPatchState(patchPath)
|
|
776
831
|
const blockRe = new RegExp(`^- id: ${escapeRegExp(id)}\\r?\\n disabled: true\\r?\\n`, 'mu')
|
|
777
832
|
if (blockRe.test(text)) {
|
|
778
|
-
await writeFile(patchPath, text.replace(blockRe, ''), 'utf8')
|
|
833
|
+
await writeFile(patchPath, sanitizePatchText(text.replace(blockRe, '')), 'utf8')
|
|
779
834
|
return { changed: true }
|
|
780
835
|
}
|
|
781
836
|
if (forced.includes(id)) return { changed: false }
|
|
782
|
-
const
|
|
837
|
+
const clean = sanitizePatchText(text)
|
|
838
|
+
const next = clean.length === 0 || clean.endsWith('\n') ? clean : `${clean}\n`
|
|
783
839
|
await writeFile(patchPath, `${next}- id: ${id}\n disabled: false\n`, 'utf8')
|
|
784
840
|
return { changed: true }
|
|
785
841
|
})
|
|
@@ -790,7 +846,8 @@ async function appendInsert(patchPath, entryId, packageName) {
|
|
|
790
846
|
return queuedWrite(async () => {
|
|
791
847
|
const { inserts, text } = await readPatchState(patchPath)
|
|
792
848
|
if (inserts.includes(entryId)) return { changed: false }
|
|
793
|
-
const
|
|
849
|
+
const clean = sanitizePatchText(text)
|
|
850
|
+
const next = clean.length === 0 || clean.endsWith('\n') ? clean : `${clean}\n`
|
|
794
851
|
const block = `- insert:\n - id: ${entryId}\n name: '${packageName}'\n`
|
|
795
852
|
await writeFile(patchPath, `${next}${block}`, 'utf8')
|
|
796
853
|
return { changed: true }
|
|
@@ -1241,31 +1298,44 @@ function githubRepoInfo(repo) {
|
|
|
1241
1298
|
}
|
|
1242
1299
|
|
|
1243
1300
|
/** raw 文件读取:https 主通道(含镜像)、gh CLI、curl 直连、curl jsDelivr CDN **四通道并行竞速**。
|
|
1244
|
-
* 黑洞期 https/ghproxy 全挂、gh 可能不在服务进程 PATH——jsDelivr CDN 走系统网络且国内可达,是最后兜底。
|
|
1301
|
+
* 黑洞期 https/ghproxy 全挂、gh 可能不在服务进程 PATH——jsDelivr CDN 走系统网络且国内可达,是最后兜底。
|
|
1302
|
+
* 关键语义:**404 是确定性结果**(文件不存在,探测 .gitmodules/SKILL.md 等时的正常答案),
|
|
1303
|
+
* 必须立即返回 null,不能等其它分支(黑洞期 https 通道 20s+ 才失败会拖死整个请求)。 */
|
|
1245
1304
|
async function rawTextWithFallback(repo, branch, path) {
|
|
1246
1305
|
const url = `${GITHUB_RAW}/${repo}/${encodeURIComponent(branch)}/${path}`
|
|
1247
|
-
|
|
1306
|
+
// 404/4xx:确定性"不存在",立即以 null 胜出(不等慢分支)
|
|
1307
|
+
const notFound = (promise) => promise
|
|
1308
|
+
.catch((error) => {
|
|
1309
|
+
if (error instanceof Error && /HTTP 404|HTTP 400|Not Found|not found|非 2xx|HTTP 非 2xx/u.test(error.message)) return null
|
|
1310
|
+
throw error
|
|
1311
|
+
})
|
|
1312
|
+
const https = notFound(githubText(url)
|
|
1248
1313
|
.then((body) => {
|
|
1249
1314
|
if (body === null) throw new Error('HTTP 非 2xx')
|
|
1250
1315
|
return body
|
|
1251
|
-
})
|
|
1252
|
-
const gh = githubViaGh(`repos/${repo}/contents/${path}?ref=${encodeURIComponent(branch)}`)
|
|
1316
|
+
}))
|
|
1317
|
+
const gh = notFound(githubViaGh(`repos/${repo}/contents/${path}?ref=${encodeURIComponent(branch)}`)
|
|
1253
1318
|
.then((data) => {
|
|
1254
1319
|
if (data && typeof data.content === 'string') return Buffer.from(data.content, 'base64').toString('utf8')
|
|
1255
1320
|
throw new Error('contents 无内容')
|
|
1256
|
-
})
|
|
1257
|
-
const curl = curlText(url,
|
|
1321
|
+
}))
|
|
1322
|
+
const curl = notFound(curlText(url, 6000)
|
|
1258
1323
|
.then((body) => {
|
|
1259
1324
|
if (body === null) throw new Error('HTTP 非 2xx')
|
|
1260
1325
|
return body
|
|
1261
|
-
})
|
|
1262
|
-
const cdn = curlText(`https://cdn.jsdelivr.net/gh/${repo}@${encodeURIComponent(branch)}/${path}`,
|
|
1326
|
+
}))
|
|
1327
|
+
const cdn = notFound(curlText(`https://cdn.jsdelivr.net/gh/${repo}@${encodeURIComponent(branch)}/${path}`, 6000)
|
|
1263
1328
|
.then((body) => {
|
|
1264
1329
|
if (body === null) throw new Error('HTTP 非 2xx')
|
|
1265
1330
|
return body
|
|
1266
|
-
})
|
|
1331
|
+
}))
|
|
1267
1332
|
try {
|
|
1268
|
-
|
|
1333
|
+
// 任一通道成功(含 404→null)立即返回;整体 5s 封顶(jsDelivr 正常 0.7s,
|
|
1334
|
+
// 黑洞期快速失败优于等 12s),超时视为不可达(null)。
|
|
1335
|
+
return await Promise.race([
|
|
1336
|
+
Promise.any([https, gh, curl, cdn]),
|
|
1337
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 5000)),
|
|
1338
|
+
])
|
|
1269
1339
|
} catch {
|
|
1270
1340
|
return null
|
|
1271
1341
|
}
|
|
@@ -1441,10 +1511,15 @@ function parseBundlePatchRefs(text) {
|
|
|
1441
1511
|
* 捆绑依赖补装(事故教训:dsh-web-ui-all 更新后 17 个捆绑依赖缺失 → 服务加载崩溃):
|
|
1442
1512
|
* curl/GitHub 通道只解压主包,这里逐个补装直接依赖(pnpm → curl 依次尝试)。
|
|
1443
1513
|
* 返回仍缺失的依赖列表。
|
|
1514
|
+
* 安全护栏(事故教训):@deepseek-ai/* 框架内部包**绝不补装**——它们由框架依赖树管理
|
|
1515
|
+
* (正确版本随 @deepseek-ai/dsh 一起安装),npm 上这些内部包的 dist-tags.latest 是远古
|
|
1516
|
+
* 版本(如 dsh-host-webserver@0.0.1-rc.1),无版本约束补装会覆盖框架正确版本,
|
|
1517
|
+
* 导致 webServer 等服务起不来、整个 profile 启动崩溃。
|
|
1444
1518
|
*/
|
|
1445
1519
|
async function backfillMissingDeps(profileDir, deps, registries) {
|
|
1446
1520
|
const stillMissing = []
|
|
1447
1521
|
for (const dep of deps) {
|
|
1522
|
+
if (/^@deepseek-ai\//u.test(dep)) continue // 框架内部包:跳过(宿主提供)
|
|
1448
1523
|
if (existsSync(join(profileDir, 'node_modules', dep))) continue
|
|
1449
1524
|
let ok = false
|
|
1450
1525
|
try {
|
|
@@ -1453,8 +1528,10 @@ async function backfillMissingDeps(profileDir, deps, registries) {
|
|
|
1453
1528
|
} catch {}
|
|
1454
1529
|
if (!ok) {
|
|
1455
1530
|
try {
|
|
1456
|
-
|
|
1457
|
-
|
|
1531
|
+
await curlManualInstall(profileDir, dep, registries)
|
|
1532
|
+
// 判断目标是否实际安装(修复:旧代码用 depInfo.missingDeps.length===0 判断
|
|
1533
|
+
// "该依赖自身无依赖",只要目标依赖带依赖就误报缺失——即使 curl 已成功安装)
|
|
1534
|
+
ok = existsSync(join(profileDir, 'node_modules', dep))
|
|
1458
1535
|
} catch {}
|
|
1459
1536
|
}
|
|
1460
1537
|
if (!ok) stillMissing.push(dep)
|
|
@@ -1503,7 +1580,8 @@ async function githubReleaseInstall(profileDir, repo, packageName) {
|
|
|
1503
1580
|
// 盒子实验:静态验证通过才覆盖(失败保留旧版本)。引用检查根用目标目录(宿主插件在根层)
|
|
1504
1581
|
const box = verifyPackageBox(pkgPath, profileDir, packageName, targetRoot)
|
|
1505
1582
|
const pkg = JSON.parse(readFileSync(join(pkgPath, 'package.json'), 'utf8'))
|
|
1506
|
-
|
|
1583
|
+
// 只统计 dependencies(peerDependencies 是宿主契约,不补装——见 curlManualInstall 注释)
|
|
1584
|
+
const deps = { ...(pkg.dependencies ?? {}) }
|
|
1507
1585
|
const missingDeps = Object.keys(deps).filter((d) => !existsSync(join(profileDir, 'node_modules', d)))
|
|
1508
1586
|
const target = join(targetRoot, packageName)
|
|
1509
1587
|
if (existsSync(target)) rmSync(target, { recursive: true, force: true })
|
|
@@ -1564,6 +1642,9 @@ async function ensureBundlePatchIntegrity(profileDir, pkgName, userPatchPath) {
|
|
|
1564
1642
|
if (refs.length === 0) return report
|
|
1565
1643
|
const registries = orderedRegistries(readSources())
|
|
1566
1644
|
for (const ref of refs) {
|
|
1645
|
+
// 框架内部包跳过(事故教训:npm 上 @deepseek-ai/* 的 dist-tags.latest 是远古版本,
|
|
1646
|
+
// 无版本约束补装会覆盖框架正确版本导致服务崩溃——见 backfillMissingDeps 注释)
|
|
1647
|
+
if (/^@deepseek-ai\//u.test(ref.name)) continue
|
|
1567
1648
|
if (existsSync(join(profileDir, 'node_modules', ref.name))) continue
|
|
1568
1649
|
let ok = false
|
|
1569
1650
|
try {
|
|
@@ -1581,7 +1662,10 @@ async function ensureBundlePatchIntegrity(profileDir, pkgName, userPatchPath) {
|
|
|
1581
1662
|
try {
|
|
1582
1663
|
const userPatch = readFileSync(userPatchPath, 'utf8')
|
|
1583
1664
|
if (!userPatch.includes(`id: ${ref.id}`)) {
|
|
1584
|
-
|
|
1665
|
+
// issue #7 防护:清理顶层 [] 占位符后再追加(模板初始文件直接追加会生成非法 YAML)
|
|
1666
|
+
const clean = sanitizePatchText(userPatch)
|
|
1667
|
+
const next = clean.length === 0 || clean.endsWith('\n') ? clean : `${clean}\n`
|
|
1668
|
+
writeFileSync(userPatchPath, `${next}- id: ${ref.id}\n disabled: true\n`, 'utf8')
|
|
1585
1669
|
report.disabled.push(ref.id)
|
|
1586
1670
|
}
|
|
1587
1671
|
} catch {}
|
|
@@ -2495,8 +2579,7 @@ async function handle(ctx, req, res) {
|
|
|
2495
2579
|
|
|
2496
2580
|
if (method === 'GET' && pathname === `${ROUTE_PREFIX}/sources`) {
|
|
2497
2581
|
const sources = readSources()
|
|
2498
|
-
|
|
2499
|
-
sendJson(res, 200, { ok: true, sources, giteeStatus: { clientConfigured: gitee.clientId !== '', hasToken: gitee.token !== '', login: gitee.login } })
|
|
2582
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources), giteeStatus: giteeStatusView(sources) })
|
|
2500
2583
|
return
|
|
2501
2584
|
}
|
|
2502
2585
|
|
|
@@ -2958,7 +3041,7 @@ async function handle(ctx, req, res) {
|
|
|
2958
3041
|
}
|
|
2959
3042
|
sources.registries.push(entry)
|
|
2960
3043
|
await writeSources(sources)
|
|
2961
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3044
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
2962
3045
|
return
|
|
2963
3046
|
}
|
|
2964
3047
|
if (action === 'remove') {
|
|
@@ -2985,7 +3068,7 @@ async function handle(ctx, req, res) {
|
|
|
2985
3068
|
target.url = url
|
|
2986
3069
|
target.name = name || url
|
|
2987
3070
|
await writeSources(sources)
|
|
2988
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3071
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
2989
3072
|
return
|
|
2990
3073
|
}
|
|
2991
3074
|
if (action === 'set-primary') {
|
|
@@ -2996,7 +3079,7 @@ async function handle(ctx, req, res) {
|
|
|
2996
3079
|
}
|
|
2997
3080
|
for (const r of sources.registries) r.primary = r.id === id
|
|
2998
3081
|
await writeSources(sources)
|
|
2999
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3082
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3000
3083
|
return
|
|
3001
3084
|
}
|
|
3002
3085
|
if (action === 'add-search') {
|
|
@@ -3028,7 +3111,7 @@ async function handle(ctx, req, res) {
|
|
|
3028
3111
|
...(headers.length > 0 ? { headers } : {}),
|
|
3029
3112
|
})
|
|
3030
3113
|
await writeSources(sources)
|
|
3031
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3114
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3032
3115
|
return
|
|
3033
3116
|
}
|
|
3034
3117
|
if (action === 'remove-search') {
|
|
@@ -3044,33 +3127,35 @@ async function handle(ctx, req, res) {
|
|
|
3044
3127
|
}
|
|
3045
3128
|
sources.searchSources = sources.searchSources.filter((s) => s.id !== id)
|
|
3046
3129
|
await writeSources(sources)
|
|
3047
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3130
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3048
3131
|
return
|
|
3049
3132
|
}
|
|
3050
3133
|
if (action === 'reset') {
|
|
3051
3134
|
const defaults = JSON.parse(JSON.stringify(DEFAULT_SOURCES))
|
|
3052
3135
|
await writeSources(defaults)
|
|
3053
|
-
sendJson(res, 200, { ok: true, sources: defaults })
|
|
3136
|
+
sendJson(res, 200, { ok: true, sources: maskSources(defaults) })
|
|
3054
3137
|
return
|
|
3055
3138
|
}
|
|
3056
3139
|
if (action === 'gitee-setup') {
|
|
3057
3140
|
const clientId = typeof body.clientId === 'string' ? body.clientId.trim() : ''
|
|
3058
3141
|
const clientSecret = typeof body.clientSecret === 'string' ? body.clientSecret.trim() : ''
|
|
3059
|
-
|
|
3142
|
+
const keepClientId = body.keepClientId === true
|
|
3143
|
+
if ((!keepClientId && !clientId) || !clientSecret) {
|
|
3060
3144
|
sendError(res, 400, 'client_id 与 client_secret 不能为空')
|
|
3061
3145
|
return
|
|
3062
3146
|
}
|
|
3063
3147
|
const current = readGiteeConfig(sources)
|
|
3064
|
-
|
|
3148
|
+
// keepClientId:前端回显的是打码 clientId(abc12345…),用户只改了 secret 时保留原配置
|
|
3149
|
+
sources.gitee = { ...current, clientId: keepClientId ? current.clientId : clientId, clientSecret }
|
|
3065
3150
|
await writeSources(sources)
|
|
3066
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3151
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3067
3152
|
return
|
|
3068
3153
|
}
|
|
3069
3154
|
if (action === 'gitee-clear') {
|
|
3070
3155
|
const current = readGiteeConfig(sources)
|
|
3071
3156
|
sources.gitee = { clientId: current.clientId, clientSecret: current.clientSecret, token: '', login: '' }
|
|
3072
3157
|
await writeSources(sources)
|
|
3073
|
-
sendJson(res, 200, { ok: true, sources })
|
|
3158
|
+
sendJson(res, 200, { ok: true, sources: maskSources(sources) })
|
|
3074
3159
|
return
|
|
3075
3160
|
}
|
|
3076
3161
|
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.11",
|
|
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",
|