@agile-team/robot-cli 3.0.0 → 3.0.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 +14 -0
- package/dist/index.js +93 -41
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [3.0.1] - 2026-03-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **下载策略全面重写** — 参考 giget (unjs/giget, 306k+ 项目使用) 的下载方式:
|
|
13
|
+
- Gitee 备用源提升为首选 (国内用户最快)
|
|
14
|
+
- 使用 `codeload.github.com` 直连 CDN 替代 `github.com/archive/` (跳过 302 重定向)
|
|
15
|
+
- 使用 `api.github.com` REST API 作为二级备选 (与 giget 一致)
|
|
16
|
+
- `github.com` 原始链接降为最后兜底
|
|
17
|
+
- **移除不稳定第三方镜像** — 移除 `hub.gitmirror.com` 和 `ghproxy.net` (不可控第三方代理)
|
|
18
|
+
- **超时优化** — 快速源 (Gitee/codeload) 30s, 慢速源 (API/github.com) 60s, 不再需要 120s
|
|
19
|
+
- **重试缩减** — 每源 2 次重试 (原 3 次), 退避时间 1s/2s (原 2s/4s), 快速失败切换下一源
|
|
20
|
+
- **错误信息增强** — 失败时列出每个源的具体错误原因
|
|
21
|
+
|
|
8
22
|
## [3.0.0] - 2026-03-28
|
|
9
23
|
|
|
10
24
|
### ⚠️ BREAKING CHANGES
|
package/dist/index.js
CHANGED
|
@@ -266,12 +266,29 @@ var START_COMMAND_MAP = {
|
|
|
266
266
|
// src/download.ts
|
|
267
267
|
var CACHE_DIR = path.join(os.homedir(), ".robot-cli", CACHE_DIR_NAME);
|
|
268
268
|
var CACHE_INDEX_PATH = path.join(CACHE_DIR, "index.json");
|
|
269
|
+
function parseGitHubRepo(repoUrl) {
|
|
270
|
+
try {
|
|
271
|
+
const url = new URL(repoUrl);
|
|
272
|
+
if (url.hostname !== "github.com") return null;
|
|
273
|
+
const parts = url.pathname.replace(/^\/|\/$/g, "").split("/");
|
|
274
|
+
if (parts.length >= 2) return { owner: parts[0], repo: parts[1] };
|
|
275
|
+
return null;
|
|
276
|
+
} catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
269
280
|
function buildDownloadUrl(repoUrl, branch = "main") {
|
|
270
281
|
try {
|
|
271
282
|
const url = new URL(repoUrl);
|
|
272
283
|
const host = url.hostname;
|
|
273
284
|
const cleanUrl = repoUrl.replace(/\/+$/, "");
|
|
274
|
-
if (host === "github.com")
|
|
285
|
+
if (host === "github.com") {
|
|
286
|
+
const gh = parseGitHubRepo(cleanUrl);
|
|
287
|
+
if (gh) return `https://codeload.github.com/${gh.owner}/${gh.repo}/zip/refs/heads/${branch}`;
|
|
288
|
+
return `${cleanUrl}/archive/refs/heads/${branch}.zip`;
|
|
289
|
+
}
|
|
290
|
+
if (host === "codeload.github.com") return `${cleanUrl}/zip/refs/heads/${branch}`;
|
|
291
|
+
if (host === "api.github.com") return cleanUrl;
|
|
275
292
|
if (host === "gitee.com")
|
|
276
293
|
return `${cleanUrl}/repository/archive/${branch}.zip`;
|
|
277
294
|
if (host === "gitlab.com") {
|
|
@@ -343,28 +360,61 @@ async function getCacheStats() {
|
|
|
343
360
|
async function clearCache() {
|
|
344
361
|
await fs.remove(CACHE_DIR);
|
|
345
362
|
}
|
|
346
|
-
var
|
|
347
|
-
var
|
|
348
|
-
var MAX_RETRIES =
|
|
349
|
-
function
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
363
|
+
var TIMEOUT_FAST = 3e4;
|
|
364
|
+
var TIMEOUT_SLOW = 6e4;
|
|
365
|
+
var MAX_RETRIES = 2;
|
|
366
|
+
function buildDownloadSources(repoUrl, branch, giteeUrl) {
|
|
367
|
+
const sources = [];
|
|
368
|
+
const gh = parseGitHubRepo(repoUrl);
|
|
369
|
+
if (giteeUrl) {
|
|
370
|
+
sources.push({
|
|
371
|
+
url: giteeUrl,
|
|
372
|
+
name: "gitee.com",
|
|
373
|
+
timeout: TIMEOUT_FAST
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
if (gh) {
|
|
377
|
+
sources.push({
|
|
378
|
+
url: `https://codeload.github.com/${gh.owner}/${gh.repo}/zip/refs/heads/${branch}`,
|
|
379
|
+
name: "codeload.github.com",
|
|
380
|
+
timeout: TIMEOUT_FAST,
|
|
381
|
+
isDirect: true
|
|
382
|
+
});
|
|
383
|
+
sources.push({
|
|
384
|
+
url: `https://api.github.com/repos/${gh.owner}/${gh.repo}/zipball/${branch}`,
|
|
385
|
+
name: "api.github.com",
|
|
386
|
+
timeout: TIMEOUT_SLOW,
|
|
387
|
+
isDirect: true,
|
|
388
|
+
headers: {
|
|
389
|
+
Accept: "application/vnd.github+json",
|
|
390
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
sources.push({
|
|
394
|
+
url: repoUrl,
|
|
395
|
+
name: "github.com",
|
|
396
|
+
timeout: TIMEOUT_SLOW
|
|
397
|
+
});
|
|
398
|
+
} else {
|
|
399
|
+
sources.push({
|
|
400
|
+
url: repoUrl,
|
|
401
|
+
name: new URL(repoUrl).hostname,
|
|
402
|
+
timeout: TIMEOUT_SLOW
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
return sources;
|
|
360
406
|
}
|
|
361
|
-
async function fetchWithRetry(downloadUrl, timeout, retries) {
|
|
407
|
+
async function fetchWithRetry(downloadUrl, timeout, retries, extraHeaders) {
|
|
362
408
|
let lastError;
|
|
363
409
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
364
410
|
try {
|
|
365
411
|
const response = await fetch(downloadUrl, {
|
|
366
412
|
signal: AbortSignal.timeout(timeout),
|
|
367
|
-
|
|
413
|
+
redirect: "follow",
|
|
414
|
+
headers: {
|
|
415
|
+
"User-Agent": "Robot-CLI/3.0",
|
|
416
|
+
...extraHeaders
|
|
417
|
+
}
|
|
368
418
|
});
|
|
369
419
|
if (!response.ok) {
|
|
370
420
|
if (response.status === 404) throw new Error(`\u4ED3\u5E93\u4E0D\u5B58\u5728 (404)`);
|
|
@@ -375,44 +425,46 @@ async function fetchWithRetry(downloadUrl, timeout, retries) {
|
|
|
375
425
|
lastError = error;
|
|
376
426
|
if (lastError.message.includes("404")) throw lastError;
|
|
377
427
|
if (attempt < retries) {
|
|
378
|
-
await new Promise((r) => setTimeout(r,
|
|
428
|
+
await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
379
429
|
}
|
|
380
430
|
}
|
|
381
431
|
}
|
|
382
432
|
throw lastError;
|
|
383
433
|
}
|
|
384
434
|
async function tryDownload(repoUrl, branch = "main", spinner, giteeUrl) {
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const current = mirrors[i];
|
|
390
|
-
const isOriginal = i === 0;
|
|
391
|
-
let sourceName;
|
|
392
|
-
try {
|
|
393
|
-
sourceName = isOriginal ? host : new URL(current.replace(/^(https?:\/\/[^/]+)\/.*/, "$1")).hostname;
|
|
394
|
-
} catch {
|
|
395
|
-
sourceName = `\u955C\u50CF ${i}`;
|
|
396
|
-
}
|
|
435
|
+
const sources = buildDownloadSources(repoUrl, branch, giteeUrl);
|
|
436
|
+
const errors = [];
|
|
437
|
+
for (let i = 0; i < sources.length; i++) {
|
|
438
|
+
const source = sources[i];
|
|
397
439
|
try {
|
|
398
|
-
if (spinner) spinner.text = `\u8FDE\u63A5 ${
|
|
399
|
-
const downloadUrl =
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
440
|
+
if (spinner) spinner.text = `\u8FDE\u63A5 ${source.name} ...`;
|
|
441
|
+
const downloadUrl = source.isDirect ? source.url : buildDownloadUrl(source.url, branch);
|
|
442
|
+
if (spinner) spinner.text = `\u4ECE ${source.name} \u4E0B\u8F7D\u6A21\u677F...`;
|
|
443
|
+
const response = await fetchWithRetry(
|
|
444
|
+
downloadUrl,
|
|
445
|
+
source.timeout,
|
|
446
|
+
MAX_RETRIES,
|
|
447
|
+
source.headers
|
|
448
|
+
);
|
|
403
449
|
if (spinner) {
|
|
404
450
|
const len = response.headers.get("content-length");
|
|
405
451
|
const sizeInfo = len ? `${(parseInt(len) / 1024 / 1024).toFixed(1)}MB ` : "";
|
|
406
|
-
spinner.text = `\u4E0B\u8F7D\u4E2D ${sizeInfo}(${
|
|
452
|
+
spinner.text = `\u4E0B\u8F7D\u4E2D ${sizeInfo}(${source.name})`;
|
|
407
453
|
}
|
|
408
|
-
return { response, sourceName };
|
|
454
|
+
return { response, sourceName: source.name };
|
|
409
455
|
} catch (error) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
456
|
+
const errMsg = error.message || String(error);
|
|
457
|
+
errors.push(`${source.name}: ${errMsg}`);
|
|
458
|
+
if (i < sources.length - 1 && spinner) {
|
|
459
|
+
spinner.text = `${source.name} \u4E0D\u53EF\u7528\uFF0C\u5207\u6362\u5230 ${sources[i + 1].name}...`;
|
|
460
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
461
|
+
}
|
|
413
462
|
}
|
|
414
463
|
}
|
|
415
|
-
throw new Error(
|
|
464
|
+
throw new Error(
|
|
465
|
+
`\u6240\u6709\u4E0B\u8F7D\u6E90\u5747\u4E0D\u53EF\u7528:
|
|
466
|
+
${errors.map((e) => ` - ${e}`).join("\n")}`
|
|
467
|
+
);
|
|
416
468
|
}
|
|
417
469
|
async function downloadTemplate(template, options = {}) {
|
|
418
470
|
const { spinner, noCache, giteeUrl: optGiteeUrl } = options;
|