@agile-team/robot-cli 3.0.1 → 3.0.2
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 +8 -0
- package/dist/index.js +14 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ 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.2] - 2026-03-29
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **根治 ZIP 下载内容校验问题** — 新增两层防护:
|
|
13
|
+
1. `Content-Type` 检测: 若服务器返回 `text/html`(登录页/CAPTCHA/跳转页)立即报错,`tryDownload` 自动切换到下一个源(codeload CDN → GitHub API → github.com)
|
|
14
|
+
2. ZIP 魔术字节校验 (`PK 50 4B`): 写入磁盘前检查 buffer 头部,若不是合法 ZIP 抛出含内容预览的可读错误,而非让 `extract-zip` 报 `end of central directory record signature not found`
|
|
15
|
+
|
|
8
16
|
## [3.0.1] - 2026-03-28
|
|
9
17
|
|
|
10
18
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -420,6 +420,10 @@ async function fetchWithRetry(downloadUrl, timeout, retries, extraHeaders) {
|
|
|
420
420
|
if (response.status === 404) throw new Error(`\u4ED3\u5E93\u4E0D\u5B58\u5728 (404)`);
|
|
421
421
|
throw new Error(`HTTP ${response.status}`);
|
|
422
422
|
}
|
|
423
|
+
const ct = response.headers.get("content-type") ?? "";
|
|
424
|
+
if (ct.includes("text/html")) {
|
|
425
|
+
throw new Error(`\u6536\u5230 HTML \u54CD\u5E94\u800C\u975E ZIP \u6587\u4EF6\uFF08\u8BE5\u6E90\u53EF\u80FD\u9700\u8981\u8BA4\u8BC1\uFF09`);
|
|
426
|
+
}
|
|
423
427
|
return response;
|
|
424
428
|
} catch (error) {
|
|
425
429
|
lastError = error;
|
|
@@ -466,6 +470,14 @@ async function tryDownload(repoUrl, branch = "main", spinner, giteeUrl) {
|
|
|
466
470
|
${errors.map((e) => ` - ${e}`).join("\n")}`
|
|
467
471
|
);
|
|
468
472
|
}
|
|
473
|
+
function assertZipBuffer(buffer, sourceName) {
|
|
474
|
+
if (buffer.length < 4 || buffer[0] !== 80 || buffer[1] !== 75) {
|
|
475
|
+
const preview = buffer.slice(0, 100).toString("utf8").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff]/g, "\xB7").replace(/\s+/g, " ").slice(0, 80);
|
|
476
|
+
throw new Error(
|
|
477
|
+
`${sourceName} \u8FD4\u56DE\u7684\u4E0D\u662F\u6709\u6548 ZIP \u6587\u4EF6 (\u5185\u5BB9: "${preview}")`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
469
481
|
async function downloadTemplate(template, options = {}) {
|
|
470
482
|
const { spinner, noCache, giteeUrl: optGiteeUrl } = options;
|
|
471
483
|
const branch = template.branch || "main";
|
|
@@ -504,9 +516,11 @@ async function downloadTemplate(template, options = {}) {
|
|
|
504
516
|
spinner.text = `\u4E0B\u8F7D\u4E2D [${bar}] ${pct}% ${sizeMB}MB/${totalMB}MB (${sourceName})`;
|
|
505
517
|
}
|
|
506
518
|
const buffer = Buffer.concat(chunks);
|
|
519
|
+
assertZipBuffer(buffer, sourceName);
|
|
507
520
|
await fs.writeFile(tempZip, buffer);
|
|
508
521
|
} else {
|
|
509
522
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
523
|
+
assertZipBuffer(buffer, sourceName);
|
|
510
524
|
await fs.writeFile(tempZip, buffer);
|
|
511
525
|
}
|
|
512
526
|
if (spinner) spinner.text = "\u89E3\u538B\u6A21\u677F\u6587\u4EF6...";
|