@having5548/dsh-downloader 0.6.0 → 0.7.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/CHANGELOG.md +51 -0
- package/lib/client.js +1343 -650
- package/lib/core/upstream.js +11 -0
- package/package.json +5 -1
- package/scripts/fix-nested-links.mjs +130 -0
package/lib/core/upstream.js
CHANGED
|
@@ -277,6 +277,12 @@ export class UpstreamManager {
|
|
|
277
277
|
await this.#saveState();
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
/** [dsh-downloader] 回到「自动选择」:清掉钉死的节点,让分组策略重新挑。 */
|
|
281
|
+
async clearSelection() {
|
|
282
|
+
this.#selected = null;
|
|
283
|
+
await this.#saveState();
|
|
284
|
+
}
|
|
285
|
+
|
|
280
286
|
/** One latency test, for the panel. */
|
|
281
287
|
async testLatency(name) {
|
|
282
288
|
const node = this.#nodes.get(name);
|
|
@@ -419,6 +425,11 @@ export class UpstreamManager {
|
|
|
419
425
|
await this.selectNode(body.name);
|
|
420
426
|
return send(200, { ok: true, selected: body.name });
|
|
421
427
|
}
|
|
428
|
+
case "select-auto": {
|
|
429
|
+
if (method !== "POST") return send(405, { error: "该接口仅支持 POST" });
|
|
430
|
+
await this.clearSelection();
|
|
431
|
+
return send(200, { ok: true, selected: null });
|
|
432
|
+
}
|
|
422
433
|
case "traffic":
|
|
423
434
|
return send(200, this.#trafficSnapshot());
|
|
424
435
|
case "guard":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@having5548/dsh-downloader",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "DeepSeek Harness 下载代理插件:注册 dsh_download 等工具,让模型下载境外文件时自动走插件自带的代理内核(订阅自包含,或显式 proxyUrl),境内直连;带 sha256、大小上限、停滞检测与直连失败自动回退,另附「设置 → 下载代理」管理面板。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"main": "lib/index.js",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"fix-links": "node scripts/fix-nested-links.mjs"
|
|
27
|
+
},
|
|
25
28
|
"exports": {
|
|
26
29
|
".": {
|
|
27
30
|
"default": "./lib/index.js"
|
|
@@ -33,6 +36,7 @@
|
|
|
33
36
|
},
|
|
34
37
|
"files": [
|
|
35
38
|
"lib",
|
|
39
|
+
"scripts",
|
|
36
40
|
"native",
|
|
37
41
|
"cordis.patch.yml",
|
|
38
42
|
"README.md",
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 修复 profile 里被 pnpm 留下「空壳」的嵌套依赖目录。
|
|
4
|
+
*
|
|
5
|
+
* 背景:DSH 的插件加载器**按每个包自己声明的 dependencies 构造路径**
|
|
6
|
+
* (`<包>/node_modules/<依赖>/<main>`)并直接 open;而 pnpm 的扁平布局有时只
|
|
7
|
+
* 建出空目录、不填内容。于是会出现:
|
|
8
|
+
*
|
|
9
|
+
* ENOENT: open '...\node_modules\@having5548\dsh-downloader\node_modules\
|
|
10
|
+
* @deepseek-ai\schemastery\lib\index.mjs'
|
|
11
|
+
*
|
|
12
|
+
* 现象是插件「failed to import」、整条 entry 不激活(连带其它插件也报同一路径)。
|
|
13
|
+
*
|
|
14
|
+
* 本脚本把「缺的嵌套依赖」指回顶层的同名包(Windows 用 junction,其它平台用
|
|
15
|
+
* 符号链接)。可反复执行:已经正确的会跳过。
|
|
16
|
+
*
|
|
17
|
+
* 用法:
|
|
18
|
+
* node scripts/fix-nested-links.mjs # 修当前目录下的 node_modules
|
|
19
|
+
* node scripts/fix-nested-links.mjs <profileDir> # 修指定 profile
|
|
20
|
+
* node scripts/fix-nested-links.mjs --dry-run # 只看要做什么
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
23
|
+
import { dirname, join, resolve } from "node:path";
|
|
24
|
+
import { execFileSync } from "node:child_process";
|
|
25
|
+
|
|
26
|
+
const args = process.argv.slice(2);
|
|
27
|
+
const dryRun = args.includes("--dry-run");
|
|
28
|
+
const profileDir = resolve(args.find((arg) => !arg.startsWith("--")) ?? process.cwd());
|
|
29
|
+
const modules = join(profileDir, "node_modules");
|
|
30
|
+
|
|
31
|
+
if (!existsSync(modules)) {
|
|
32
|
+
console.error(`找不到 ${modules} —— 参数应当是 profile 目录(含 node_modules 的那层)`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 枚举 node_modules 下的包目录(含 @scope/name 形式)。 */
|
|
37
|
+
function listPackages() {
|
|
38
|
+
const found = [];
|
|
39
|
+
for (const entry of readdirSync(modules, { withFileTypes: true })) {
|
|
40
|
+
if (!entry.isDirectory()) continue;
|
|
41
|
+
if (entry.name === ".bin" || entry.name.startsWith(".")) continue;
|
|
42
|
+
if (entry.name.startsWith("@")) {
|
|
43
|
+
for (const inner of readdirSync(join(modules, entry.name), { withFileTypes: true })) {
|
|
44
|
+
if (inner.isDirectory()) found.push(`${entry.name}/${inner.name}`);
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
found.push(entry.name);
|
|
49
|
+
}
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readJson(file) {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function link(from, to) {
|
|
62
|
+
if (process.platform === "win32") {
|
|
63
|
+
execFileSync("cmd", ["/c", "mklink", "/J", from, to], { stdio: "pipe" });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
execFileSync("ln", ["-s", to, from], { stdio: "pipe" });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const fixed = [];
|
|
70
|
+
const skipped = [];
|
|
71
|
+
|
|
72
|
+
for (const name of listPackages()) {
|
|
73
|
+
const pkgDir = join(modules, name);
|
|
74
|
+
const manifest = readJson(join(pkgDir, "package.json"));
|
|
75
|
+
if (manifest === null) continue;
|
|
76
|
+
const deps = Object.keys(manifest.dependencies ?? {});
|
|
77
|
+
if (deps.length === 0) continue;
|
|
78
|
+
|
|
79
|
+
for (const dep of deps) {
|
|
80
|
+
const nested = join(pkgDir, "node_modules", dep);
|
|
81
|
+
const hoisted = join(modules, dep);
|
|
82
|
+
// 已经有内容就不管;只有「不存在 / 空目录 / 断链」才修。
|
|
83
|
+
let needsFix = false;
|
|
84
|
+
if (!existsSync(nested)) {
|
|
85
|
+
needsFix = true;
|
|
86
|
+
} else {
|
|
87
|
+
try {
|
|
88
|
+
const stat = lstatSync(nested);
|
|
89
|
+
if (stat.isDirectory() && readdirSync(nested).length === 0) needsFix = true;
|
|
90
|
+
} catch {
|
|
91
|
+
needsFix = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!needsFix) continue;
|
|
95
|
+
if (!existsSync(hoisted)) {
|
|
96
|
+
skipped.push(`${dep}(顶层也没有)`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (statSync(hoisted) === undefined) continue;
|
|
100
|
+
const parent = dirname(nested);
|
|
101
|
+
const relative = `${name} → ${dep}`;
|
|
102
|
+
if (dryRun) {
|
|
103
|
+
fixed.push(`[dry-run] ${relative}`);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
if (existsSync(nested)) {
|
|
108
|
+
// 空目录:先删掉再建链接
|
|
109
|
+
execFileSync(process.platform === "win32" ? "cmd" : "rm", process.platform === "win32" ? ["/c", "rmdir", nested] : ["-rf", nested], { stdio: "pipe" });
|
|
110
|
+
}
|
|
111
|
+
if (!existsSync(parent)) {
|
|
112
|
+
execFileSync(process.platform === "win32" ? "cmd" : "mkdir", process.platform === "win32" ? ["/c", "mkdir", parent] : ["-p", parent], { stdio: "pipe" });
|
|
113
|
+
}
|
|
114
|
+
link(nested, hoisted);
|
|
115
|
+
fixed.push(relative);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
skipped.push(`${relative} —— ${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`profile: ${profileDir}`);
|
|
123
|
+
console.log(`已修 ${fixed.length} 条${dryRun ? "(干跑)" : ""}`);
|
|
124
|
+
for (const line of fixed) console.log(` + ${line}`);
|
|
125
|
+
if (skipped.length > 0) {
|
|
126
|
+
console.log(`跳过 ${skipped.length} 条`);
|
|
127
|
+
for (const line of skipped) console.log(` - ${line}`);
|
|
128
|
+
}
|
|
129
|
+
console.log("");
|
|
130
|
+
console.log("修完重启 DSH,或对受影响的插件重新启用一次。");
|