baiduwangpan-cli 1.0.2 → 1.1.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.
@@ -4,8 +4,15 @@
4
4
  * postinstall.js — 自动下载 BaiduPCS-Go 二进制
5
5
  *
6
6
  * 跨平台支持: Windows / macOS / Linux (x64, arm64, 386)
7
- * 下载加速: GitHub → ghproxy 镜像 → ghfast 镜像 → 降级提示
7
+ * 下载加速: 镜像(ghfast/ghproxy...) 优先 → GitHub 兜底(大陆网络 GitHub 常 DNS 污染)
8
8
  * 解压方式: Windows (PowerShell) / Unix (unzip / python3 / tar)
9
+ *
10
+ * 1.1.0 已知问题修复:
11
+ * - 镜像顺序: 国内网络优先用 ghfast/ghproxy,GitHub 放最后
12
+ * - download() 补 file stream error 处理:第一个镜像部分下载失败时不再直接崩溃,
13
+ * 确保 5 个源按顺序 fallback(此前"只跑 1 行就抛出")
14
+ * - 全部失败时打印每个源的完整下载地址,便于手动下载
15
+ * - 发现版本子目录(BaiduPCS-Go-vX/)里的二进制时提升到固定路径(修复后无需手改 config)
9
16
  */
10
17
 
11
18
  const https = require("https");
@@ -19,13 +26,13 @@ const PKG_ROOT = path.join(__dirname, "..");
19
26
  const REPO = "qjfoidnh/BaiduPCS-Go";
20
27
  const VERSION = "v4.0.1";
21
28
 
22
- // GitHub 下载加速镜像(按优先级排列)
29
+ // GitHub 下载加速镜像(按优先级排列):大陆网络优先镜像,GitHub 放最后兜底
23
30
  const MIRRORS = [
24
- (asset) => `https://github.com/${REPO}/releases/download/${VERSION}/${asset}`,
25
- (asset) => `https://ghproxy.net/https://github.com/${REPO}/releases/download/${VERSION}/${asset}`,
26
31
  (asset) => `https://ghfast.top/https://github.com/${REPO}/releases/download/${VERSION}/${asset}`,
32
+ (asset) => `https://ghproxy.net/https://github.com/${REPO}/releases/download/${VERSION}/${asset}`,
27
33
  (asset) => `https://mirror.ghproxy.com/https://github.com/${REPO}/releases/download/${VERSION}/${asset}`,
28
34
  (asset) => `https://gh-proxy.com/https://github.com/${REPO}/releases/download/${VERSION}/${asset}`,
35
+ (asset) => `https://github.com/${REPO}/releases/download/${VERSION}/${asset}`,
29
36
  ];
30
37
 
31
38
  // ── 平台检测 ────────────────────────────────────────────
@@ -84,67 +91,69 @@ function download(url, dest, timeout = 60000) {
84
91
  const timer = setTimeout(() => {
85
92
  if (!settled) {
86
93
  settled = true;
87
- file.close();
94
+ try { file.close(); } catch {}
88
95
  fs.unlink(dest, () => {});
89
96
  reject(new Error("timeout"));
90
97
  }
91
98
  }, timeout);
92
99
 
100
+ // 统一失败路径:清定时器 + 关流 + 删残留文件。file stream 的 error 也走这里,
101
+ // 防止第一个镜像部分下载失败后 file 抛 EPIPE/ENOSPC 未捕获导致整个 postinstall 崩溃
102
+ // (1.1.0 实测"只跑 1 行就抛出",fallback 没机会执行)。
103
+ const fail = (err) => {
104
+ if (settled) return;
105
+ settled = true;
106
+ clearTimeout(timer);
107
+ try { file.close(); } catch {}
108
+ fs.unlink(dest, () => {});
109
+ reject(err);
110
+ };
111
+
112
+ file.on("error", (e) => fail(e));
113
+
93
114
  const req = (url, redirects = 0) => {
94
115
  if (redirects > 5) {
95
- if (!settled) { settled = true; clearTimeout(timer); reject(new Error("too many redirects")); }
116
+ fail(new Error("too many redirects"));
96
117
  return;
97
118
  }
98
119
 
99
120
  const client = url.startsWith("https") ? https : http;
100
- const r = client.get(url, { timeout: 15000 }, (res) => {
101
- // 处理重定向
102
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
103
- res.resume();
104
- req(res.headers.location, redirects + 1);
105
- return;
106
- }
107
- if (res.statusCode !== 200) {
108
- res.resume();
109
- if (!settled) {
110
- settled = true;
111
- clearTimeout(timer);
112
- file.close();
113
- fs.unlink(dest, () => {});
114
- reject(new Error(`HTTP ${res.statusCode}`));
121
+ let r;
122
+ try {
123
+ r = client.get(url, { timeout: 15000 }, (res) => {
124
+ // 处理重定向
125
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
126
+ res.resume();
127
+ req(res.headers.location, redirects + 1);
128
+ return;
115
129
  }
116
- return;
117
- }
118
- res.pipe(file);
119
- file.on("finish", () => {
120
- file.close(() => {
121
- if (!settled) {
122
- settled = true;
123
- clearTimeout(timer);
124
- resolve();
125
- }
130
+ if (res.statusCode !== 200) {
131
+ res.resume();
132
+ fail(new Error(`HTTP ${res.statusCode}`));
133
+ return;
134
+ }
135
+ res.pipe(file);
136
+ file.on("finish", () => {
137
+ file.close(() => {
138
+ if (!settled) {
139
+ settled = true;
140
+ clearTimeout(timer);
141
+ resolve();
142
+ }
143
+ });
126
144
  });
127
145
  });
128
- });
146
+ } catch (e) {
147
+ fail(e);
148
+ return;
149
+ }
129
150
 
130
- r.on("error", (e) => {
131
- if (!settled) {
132
- settled = true;
133
- clearTimeout(timer);
134
- file.close();
135
- fs.unlink(dest, () => {});
136
- reject(e);
137
- }
138
- });
151
+ r.on("error", (e) => fail(e));
139
152
 
140
153
  r.on("timeout", () => {
141
154
  if (!settled) {
142
- settled = true;
143
- clearTimeout(timer);
144
155
  r.destroy();
145
- file.close();
146
- fs.unlink(dest, () => {});
147
- reject(new Error("connection timeout"));
156
+ fail(new Error("connection timeout"));
148
157
  }
149
158
  });
150
159
  };
@@ -154,20 +163,29 @@ function download(url, dest, timeout = 60000) {
154
163
  }
155
164
 
156
165
  async function downloadWithMirrors(assetName, dest) {
166
+ const errors = [];
157
167
  for (let i = 0; i < MIRRORS.length; i++) {
158
168
  const url = MIRRORS[i](assetName);
159
- const label = i === 0 ? "GitHub" : new URL(MIRRORS[i]("x")).hostname;
169
+ const label = i === MIRRORS.length - 1 ? "GitHub" : new URL(MIRRORS[i]("x")).hostname;
160
170
  process.stdout.write(` bdp: trying ${label}...`);
161
171
  try {
162
172
  await download(url, dest, 90000);
163
173
  console.log(" OK");
164
- return true;
174
+ return { ok: true, url, label };
165
175
  } catch (e) {
166
- console.log(` failed (${e.message})`);
167
- if (fs.existsSync(dest)) fs.unlinkSync(dest);
176
+ const msg = e.message || String(e);
177
+ errors.push(`${label}: ${msg}`);
178
+ console.log(` failed (${msg})`);
179
+ if (fs.existsSync(dest)) {
180
+ try { fs.unlinkSync(dest); } catch {}
181
+ }
182
+ // GitHub DNS 污染提示
183
+ if (/ENOTFOUND|EAI_AGAIN|ETIMEDOUT/i.test(msg) && label === "GitHub") {
184
+ console.log(" bdp: (GitHub 解析失败——大陆网络常见 DNS 污染,前面的镜像通常可用)");
185
+ }
168
186
  }
169
187
  }
170
- return false;
188
+ return { ok: false, errors };
171
189
  }
172
190
 
173
191
  // ── 解压 ────────────────────────────────────────────────
@@ -213,13 +231,39 @@ function extractZip(zipPath, destDir) {
213
231
 
214
232
  // ── 主逻辑 ──────────────────────────────────────────────
215
233
 
234
+ function findExistingBinary(binName) {
235
+ const binPath = path.join(PKG_ROOT, binName);
236
+ if (fs.existsSync(binPath)) return { path: binPath, versioned: false };
237
+ // 版本子目录(上次 postinstall 失败后手动解压的常见位置)
238
+ try {
239
+ for (const entry of fs.readdirSync(PKG_ROOT, { withFileTypes: true })) {
240
+ if (entry.isDirectory() && entry.name.includes("BaiduPCS-Go")) {
241
+ const p = path.join(PKG_ROOT, entry.name, binName);
242
+ if (fs.existsSync(p)) return { path: p, versioned: true };
243
+ }
244
+ }
245
+ } catch {}
246
+ return null;
247
+ }
248
+
216
249
  async function main() {
217
250
  const binName = getBinaryName();
218
251
  const binPath = path.join(PKG_ROOT, binName);
219
252
 
220
- // 已存在则跳过
221
- if (fs.existsSync(binPath)) {
222
- console.log(" bdp: BaiduPCS-Go already exists, skipping.");
253
+ // 已存在则跳过;版本子目录里的二进制提升到固定路径(CLI 固定路径优先,且不依赖版本号)
254
+ const existing = findExistingBinary(binName);
255
+ if (existing) {
256
+ if (existing.versioned) {
257
+ try {
258
+ fs.copyFileSync(existing.path, binPath);
259
+ if (os.platform() !== "win32") { try { fs.chmodSync(binPath, 0o755); } catch {} }
260
+ console.log(` bdp: ✅ BaiduPCS-Go promoted from ${path.basename(path.dirname(existing.path))} to fixed path.`);
261
+ } catch (e) {
262
+ console.log(` bdp: ⚠️ 版本子目录已发现但提升失败: ${e.message}`);
263
+ }
264
+ } else {
265
+ console.log(" bdp: BaiduPCS-Go already exists, skipping.");
266
+ }
223
267
  return;
224
268
  }
225
269
 
@@ -229,14 +273,15 @@ async function main() {
229
273
  console.log("");
230
274
  console.log(" bdp: Installing BaiduPCS-Go (百度网盘 CLI 引擎)...");
231
275
 
232
- // 下载
233
- const ok = await downloadWithMirrors(assetName, zipPath);
234
- if (!ok) {
235
- console.warn(" bdp: ⚠️ All download mirrors failed.");
236
- console.warn(` bdp: Please manually download ${assetName}`);
237
- console.warn(` bdp: from https://github.com/${REPO}/releases/tag/${VERSION}`);
238
- console.warn(` bdp: and extract ${binName} to: ${PKG_ROOT}`);
239
- console.warn(" bdp: (the package still works if BaiduPCS-Go is in your PATH)");
276
+ // 下载(镜像优先 → GitHub 兜底)
277
+ const result = await downloadWithMirrors(assetName, zipPath);
278
+ if (!result.ok) {
279
+ console.warn(" bdp: ⚠️ 所有下载源失败(大陆网络常见:GitHub DNS 污染 / 镜像暂时抽风)。");
280
+ console.warn(` bdp: 请手动下载 ${assetName}(任一地址均可):`);
281
+ for (const m of MIRRORS) console.warn(` bdp: ${m(assetName)}`);
282
+ console.warn(` bdp: 解压后把 ${binName} 放到: ${PKG_ROOT}`);
283
+ console.warn(" bdp: (或版本子目录,如 BaiduPCS-Go-v4.0.1-windows-x64/,CLI 会自动发现)");
284
+ console.warn(" bdp: 也可直接确保 BaiduPCS-Go 在 PATH 中(包仍可正常使用)。");
240
285
  console.warn("");
241
286
  process.exit(0); // 不阻断 npm install
242
287
  }
@@ -244,7 +289,7 @@ async function main() {
244
289
  // 解压
245
290
  console.log(" bdp: Extracting...");
246
291
  if (!extractZip(zipPath, PKG_ROOT)) {
247
- console.warn(` bdp: ⚠️ Cannot extract. Please manually unzip: ${zipPath}`);
292
+ console.warn(` bdp: ⚠️ 解压失败。请手动解压: ${zipPath}`);
248
293
  process.exit(0);
249
294
  }
250
295
 
@@ -273,8 +318,8 @@ async function main() {
273
318
  }
274
319
  console.log(` bdp: ✅ BaiduPCS-Go installed.`);
275
320
  } else {
276
- console.warn(" bdp: ⚠️ Binary not found after extraction.");
277
- console.warn(` bdp: Please manually extract and place ${binName} in: ${PKG_ROOT}`);
321
+ console.warn(" bdp: ⚠️ 解压后未找到二进制。");
322
+ console.warn(` bdp: 请手动解压并把 ${binName} 放到: ${PKG_ROOT}`);
278
323
  }
279
324
 
280
325
  // 清理 zip 和解压目录