@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 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...";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agile-team/robot-cli",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "现代化项目脚手架工具,支持多技术栈快速创建项目 - 优先 bun,兼容 npm/pnpm/yarn",
5
5
  "type": "module",
6
6
  "bin": {