dsh-pocket 2.6.0 → 2.6.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/README.en.md CHANGED
@@ -194,7 +194,7 @@ Such tools take over all traffic and often cut cloudflared's tunnel-edge connect
194
194
  ```sh
195
195
  npm install
196
196
  node client/build.mjs # rebuild after editing client/
197
- npm test # proxy / auth / compression / tunnel / service / RPC / settings (105 tests)
197
+ npm test # proxy / auth / compression / tunnel / service / RPC / settings (108 tests)
198
198
  ```
199
199
 
200
200
  **Want to try your changes locally without publishing?** Point the installed plugin at your local checkout with a symlink and restart dsh web. Full steps (including switching back to the npm release) are in [LOCAL-DEV.md](./LOCAL-DEV.md).
package/README.md CHANGED
@@ -195,7 +195,7 @@ npx @deepseek-ai/dsh web
195
195
  ```sh
196
196
  npm install
197
197
  node client/build.mjs # 改 client/ 后重新打包
198
- npm test # 代理 / 认证 / 压缩 / 隧道 / 服务 / RPC / 设置(105 测试)
198
+ npm test # 代理 / 认证 / 压缩 / 隧道 / 服务 / RPC / 设置(108 测试)
199
199
  ```
200
200
 
201
201
  **改完想在本机先试?** 不用发版:把插件换成指向本地仓库的软链,重启 dsh web 就是本地代码。完整步骤(含怎么换回 npm 官方版本)见 [LOCAL-DEV.md](./LOCAL-DEV.md)。
package/lib/tunnel.mjs CHANGED
@@ -23,12 +23,37 @@ import { createWriteStream } from 'node:fs';
23
23
  export const QUICK_TUNNEL_URL_RE = /https:\/\/(?!api\.)[a-z0-9-]+\.trycloudflare\.com/i;
24
24
 
25
25
  function platformBinary() {
26
- const archMap = { x64: 'amd64', arm64: 'arm64' };
26
+ const archMap = { x64: 'amd64', arm64: 'arm64', ia32: '386', arm: 'arm' };
27
27
  const a = archMap[process.arch] ?? process.arch;
28
28
  const os = process.platform === 'darwin' ? 'darwin' : process.platform === 'win32' ? 'windows' : 'linux';
29
29
  return { os, a, ext: os === 'windows' ? '.exe' : '' };
30
30
  }
31
31
 
32
+ /**
33
+ * 候选发布资产名,按优先级排列(issue #45)。
34
+ *
35
+ * cloudflared 现在的发布布局是**按平台分**:
36
+ * - linux:只有裸二进制 `cloudflared-linux-amd64`(没有 .tgz)
37
+ * - darwin:只有 `cloudflared-darwin-<arch>.tgz`
38
+ * - windows:只有 `cloudflared-windows-<arch>.exe`
39
+ *
40
+ * 我们以前给 linux 拼的是 `cloudflared-linux-amd64.tgz`,而它**根本不存在**
41
+ * (GitHub 返回 404),五个镜像全指向同一个 404 地址,于是必然"所有源都不通"
42
+ * —— 表现就是 Linux 服务器上开了公网访问一直报"无法安装",而 macOS/Windows
43
+ * 一切正常。Linux 下裸二进制还有个额外好处:不用解压,也就不再依赖系统装了
44
+ * `tar`(Alpine / slim 镜像 / 容器里常常没有)。
45
+ *
46
+ * linux 仍把 .tgz 留在候选里作为回退,万一上游改回打包方式也不会直接躺平。
47
+ *
48
+ * @returns {string[]} 资产名列表,越靠前越优先。
49
+ */
50
+ export function platformAssets() {
51
+ const { os, a } = platformBinary();
52
+ if (os === 'windows') return [`cloudflared-windows-${a}.exe`];
53
+ if (os === 'darwin') return [`cloudflared-darwin-${a}.tgz`];
54
+ return [`cloudflared-linux-${a}`, `cloudflared-linux-${a}.tgz`];
55
+ }
56
+
32
57
  /**
33
58
  * cloudflared 下载源。
34
59
  * 优先:清华 TUNA 镜像的 Homebrew bottle(国内 CDN,实测 ~3MB/s)——仅 macOS/Linux
@@ -189,51 +214,59 @@ async function downloadCloudflared(binPath, signal) {
189
214
  const dir = dirname(binPath);
190
215
  const tmpFile = join(dir, `cloudflared.download`);
191
216
  const isWindows = os === 'windows';
192
- // 发布资产:Windows 是 .exe(下载即二进制),macOS/Linux 是 .tgz(需解压)
193
- const asset = isWindows ? `cloudflared-windows-${a}.exe` : `cloudflared-${os}-${a}.tgz`;
194
217
  const fetchSignal = signal
195
218
  ? AbortSignal.any([signal, AbortSignal.timeout(120_000)])
196
219
  : AbortSignal.timeout(120_000);
197
220
 
198
- // 构建有序源列表:[{url, host}];清华(如有,仅 macOS/Linux)排第一,再官方 + 加速源
199
- const sources = [];
200
- if (!isWindows) {
201
- const tua = await tsinghuaBottleUrl({ os, a }).catch(() => null);
202
- if (tua) sources.push({ url: tua, host: 'mirrors.tuna.tsinghua.edu.cn' });
203
- }
204
- for (const m of CLOUDFLARED_MIRRORS) sources.push({ url: m(asset), host: hostOf(m(asset)) });
221
+ // 清华 bottle(仅 macOS/Linux 且有对应 bottle);与资产名无关,只算一次
222
+ const bottle = isWindows ? null : await tsinghuaBottleUrl({ os, a }).catch(() => null);
205
223
 
224
+ // 逐个资产试(linux 首选裸二进制,.tgz 只是回退),每个资产再逐个源试
225
+ const assets = platformAssets();
206
226
  let lastErr = null;
207
- for (let i = 0; i < sources.length; i++) {
208
- const { url, host } = sources[i];
209
- console.log(`⬇️ 下载 cloudflared(${i + 1}/${sources.length}:${host})…`);
210
- try {
211
- // 多线程分块(官方 GitHub 支持 Range,Windows 50MB 从几分钟降到几十秒);
212
- // 不支持 Range 的源自动回退单线程
213
- await downloadFile(url, tmpFile, { signal: fetchSignal });
214
- // 简单校验:空文件/极小文件视为下载失败(可能是镜像返回了错误页)
215
- const st = await stat(tmpFile);
216
- if (st.size < 1024 * 1024) throw new Error(`文件异常小(${st.size} 字节),疑似镜像错误页`);
217
- lastErr = null;
218
- break; // 下载成功
219
- } catch (err) {
220
- lastErr = err;
221
- await rm(tmpFile, { force: true }).catch(() => {}); // 清掉半截文件
222
- console.warn(` ⚠️ ${i + 1} 失败:${err?.message ?? err},尝试下一个…`);
227
+ let usedAsset = null;
228
+
229
+ for (let ai = 0; ai < assets.length && usedAsset === null; ai++) {
230
+ const asset = assets[ai];
231
+ const sources = [];
232
+ if (bottle && asset.endsWith('.tgz')) {
233
+ sources.push({ url: bottle, host: 'mirrors.tuna.tsinghua.edu.cn' });
234
+ }
235
+ for (const m of CLOUDFLARED_MIRRORS) sources.push({ url: m(asset), host: hostOf(m(asset)) });
236
+
237
+ for (let i = 0; i < sources.length; i++) {
238
+ const { url, host } = sources[i];
239
+ console.log(`⬇️ 下载 cloudflared(${asset},源 ${i + 1}/${sources.length}:${host})…`);
240
+ try {
241
+ // 多线程分块(官方 GitHub 支持 Range,Windows 50MB 从几分钟降到几十秒);
242
+ // 不支持 Range 的源自动回退单线程
243
+ await downloadFile(url, tmpFile, { signal: fetchSignal });
244
+ // 简单校验:空文件/极小文件视为下载失败(可能是镜像返回了错误页)
245
+ const st = await stat(tmpFile);
246
+ if (st.size < 1024 * 1024) throw new Error(`文件异常小(${st.size} 字节),疑似镜像错误页`);
247
+ usedAsset = asset;
248
+ lastErr = null;
249
+ break; // 下载成功
250
+ } catch (err) {
251
+ lastErr = err;
252
+ await rm(tmpFile, { force: true }).catch(() => {}); // 清掉半截文件
253
+ console.warn(` ⚠️ 源 ${i + 1} 失败:${err?.message ?? err},尝试下一个…`);
254
+ }
223
255
  }
224
256
  }
225
- if (lastErr) {
257
+ if (usedAsset === null) {
226
258
  throw new Error(
227
259
  `cloudflared 下载失败:所有源都不通(最后错误:${lastErr?.message ?? lastErr})。`
228
260
  + (isWindows
229
- ? `Windows 可手动安装后重试:winget install cloudflared;或下载 ${asset} 放到 ${dir} 目录 | download failed — try: winget install cloudflared, or put the exe into ${dir}`
230
- : `可手动安装后重试:npm i -g cloudflared(装好命令行 cloudflared 即可,无需下载);或开启代理/换网络后重试 | all mirrors failed — install cloudflared manually: npm i -g cloudflared, then retry`),
261
+ ? `Windows 可手动安装后重试:winget install cloudflared;或下载 ${assets[0]} 放到 ${dir} 目录 | download failed — try: winget install cloudflared, or put the exe into ${dir}`
262
+ : `也可以自己装好后在 settings.json "cloudflaredPath": "/path/to/cloudflared" 跳过下载(或用环境变量 DSH_POCKET_CLOUDFLARED);或用包管理器安装:apt/dnf install cloudflared | all mirrors failed — install cloudflared manually and set "cloudflaredPath" in settings.json, or: apt/dnf install cloudflared`),
231
263
  );
232
264
  }
233
265
 
234
266
  let extracted = join(dir, `cloudflared${ext}`);
235
- if (isWindows) {
236
- // Windows:exe 直接就是二进制,无需解压
267
+ // 只有 .tgz 才需要解压:Windows 的 .exe 和 linux 的裸二进制本身就可直接执行
268
+ // (linux 用裸二进制还有个好处——不依赖系统装了 tar,见 platformAssets 的说明)
269
+ if (!usedAsset.endsWith('.tgz')) {
237
270
  await rename(tmpFile, extracted).catch(async () => {
238
271
  await cp(tmpFile, extracted).catch(() => {});
239
272
  });
@@ -246,7 +279,13 @@ async function downloadCloudflared(binPath, signal) {
246
279
  await new Promise((resolve, reject) => {
247
280
  const child = spawn('tar', ['-xzf', tmpFile, '-C', extractDir], { stdio: 'ignore' });
248
281
  child.once('exit', (code) => code === 0 ? resolve() : reject(new Error(`cloudflared 解压失败(code=${code})`)));
249
- child.once('error', reject);
282
+ // spawn 失败(Alpine / slim 镜像 / 容器里常见:根本没有 tar)时 err.code
283
+ // ENOENT,直接抛出来只有一句 "spawn tar ENOENT",看不出该怎么办
284
+ child.once('error', (err) => reject(
285
+ err?.code === 'ENOENT'
286
+ ? new Error(`系统里没有 tar 命令,无法解压 ${usedAsset} —— 可改用 linux 裸二进制(默认已如此)或手动安装后设置 cloudflaredPath | no tar on this system`)
287
+ : err,
288
+ ));
250
289
  });
251
290
  // 找真实的二进制**文件**(排除目录):
252
291
  // - GitHub tgz:extractDir/cloudflared
package/package.json CHANGED
@@ -80,5 +80,5 @@
80
80
  "access": "public",
81
81
  "registry": "https://registry.npmjs.org/"
82
82
  },
83
- "version": "2.6.0"
83
+ "version": "2.6.1"
84
84
  }