@cc-claw/code 0.6.161 → 0.6.163

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/install.js CHANGED
@@ -124,6 +124,47 @@ function extractZip(buffer, dest) {
124
124
  zip.extractAllTo(dest, true);
125
125
  }
126
126
 
127
+ // 解压 ripgrep 的 tar.gz 到 dest。
128
+ // 上游 microsoft/ripgrep-prebuilt 的资产把 rg 直接放在根目录(`rg`),
129
+ // 不带 ripgrep-x.y.z-suffix/ 子目录;早先的实现假定存在该子目录并使用
130
+ // --strip-components=1,导致在所有 Linux/macOS 平台上都解不出 rg,
131
+ // 包内 bin/rg 永不落地、只能降级到系统 rg。此处先列目录判断实际布局,
132
+ // 两种结构都兼容,且把临时文件清理放进 finally 避免失败路径泄漏。
133
+ function extractRgTarGz(buffer, dest) {
134
+ const tmpFile = join(dest, "rg-download.tar.gz");
135
+ writeFileSync(tmpFile, buffer);
136
+ try {
137
+ let hasRootRg = false;
138
+ try {
139
+ const listing = execSync(`tar tzf "${tmpFile}"`, {
140
+ stdio: ["ignore", "pipe", "ignore"],
141
+ }).toString();
142
+ hasRootRg = listing
143
+ .split("\n")
144
+ .map((line) => line.trim())
145
+ // 兼容 "./rg" 前缀与 "rg" 两种写法
146
+ .some((line) => line === "rg" || line === "./rg");
147
+ } catch {
148
+ hasRootRg = false;
149
+ }
150
+
151
+ if (hasRootRg) {
152
+ execSync(`tar -xzf "${tmpFile}" -C "${dest}"`, { stdio: "ignore" });
153
+ } else {
154
+ execSync(
155
+ `tar -xzf "${tmpFile}" -C "${dest}" --strip-components=1 --wildcards "*/rg"`,
156
+ { stdio: "ignore" }
157
+ );
158
+ }
159
+ } finally {
160
+ try {
161
+ unlinkSync(tmpFile);
162
+ } catch {
163
+ // 临时文件清理失败不应影响安装
164
+ }
165
+ }
166
+ }
167
+
127
168
  // 校验下载的二进制包(#290):release 流程会发布 checksums.txt(sha256sum 格式),
128
169
  // 此处下载并校验;缺失或不匹配时直接失败(fail-closed),拒绝安装可疑二进制。
129
170
  async function verifyChecksum(buffer, fileName) {
@@ -408,11 +449,7 @@ async function downloadRipgrep(platform, binDir) {
408
449
 
409
450
  // 解压 rg 二进制到 binDir
410
451
  if (rgExt === "tar.gz") {
411
- const tmpFile = join(binDir, "rg-download.tar.gz");
412
- writeFileSync(tmpFile, rgBuffer);
413
- // ripgrep tar.gz 内含目录 ripgrep-x.y.z-suffix/rg
414
- execSync(`tar -xzf "${tmpFile}" -C "${binDir}" --strip-components=1 --wildcards "*/rg" 2>/dev/null || tar -xzf "${tmpFile}" -C "${binDir}" --strip-components=1 "ripgrep-v${RG_VERSION}-${platform.rgSuffix}/rg"`, { stdio: "ignore" });
415
- unlinkSync(tmpFile);
452
+ extractRgTarGz(rgBuffer, binDir);
416
453
  } else {
417
454
  const AdmZip = require("adm-zip");
418
455
  const zip = new AdmZip(rgBuffer);
@@ -642,4 +679,5 @@ module.exports = {
642
679
  verifyChecksum,
643
680
  replaceBinary,
644
681
  cleanOldBinaries,
682
+ extractRgTarGz,
645
683
  };
package/install.test.js CHANGED
@@ -1,8 +1,10 @@
1
- const { migrateFromClaudeCode } = require("./install");
1
+ const { migrateFromClaudeCode, extractRgTarGz } = require("./install");
2
2
 
3
3
  const fs = require("fs");
4
4
  const { join } = require("path");
5
5
  const os = require("os");
6
+ const { execSync } = require("child_process");
7
+ const { dirname } = require("path");
6
8
 
7
9
  function makeTempDir() {
8
10
  return fs.mkdtempSync(join(os.tmpdir(), "cc-code-install-test-"));
@@ -401,4 +403,77 @@ test("linkProjectMemories is idempotent and no-ops without project memory", (hom
401
403
  });
402
404
 
403
405
  // 由 runTests() 顺序执行(支持 async 用例)
406
+ // --- extractRgTarGz(#620)---------------------------------------------------
407
+ // 上游 ripgrep-prebuilt 的 tar.gz 把 rg 放在根目录,旧实现用
408
+ // --strip-components=1 导致解不出,bin/rg 永不落地。
409
+
410
+ function makeTarGz(dir, entries) {
411
+ // entries: [{ name, content, mode }]
412
+ const stage = fs.mkdtempSync(join(os.tmpdir(), "cc-code-rg-stage-"));
413
+ for (const e of entries) {
414
+ const target = join(stage, e.name);
415
+ fs.mkdirSync(dirname(target), { recursive: true });
416
+ fs.writeFileSync(target, e.content);
417
+ if (e.mode) fs.chmodSync(target, e.mode);
418
+ }
419
+ const out = join(dir, "rg-fixture.tar.gz");
420
+ // 用具体条目名打包(不用 "."),避免引入额外的 "./" 层级,
421
+ // 与上游 ripgrep-prebuilt 资产的实际结构保持一致。
422
+ const names = [...new Set(entries.map((e) => e.name.split("/")[0]))].join(" ");
423
+ execSync(`tar -czf "${out}" -C "${stage}" ${names}`, { stdio: "ignore" });
424
+ const buf = fs.readFileSync(out);
425
+ fs.rmSync(out, { force: true });
426
+ fs.rmSync(stage, { recursive: true, force: true });
427
+ return buf;
428
+ }
429
+
430
+ test("extractRgTarGz extracts rg when it sits at archive root", (home) => {
431
+ const dest = join(home, "bin");
432
+ fs.mkdirSync(dest, { recursive: true });
433
+ const buf = makeTarGz(home, [{ name: "rg", content: "#!/bin/sh\necho rg\n", mode: 0o755 }]);
434
+ extractRgTarGz(buf, dest);
435
+ const rg = join(dest, "rg");
436
+ if (!fs.existsSync(rg)) throw new Error("rg was not extracted from root layout");
437
+ if (fs.readFileSync(rg, "utf-8").indexOf("echo rg") === -1) {
438
+ throw new Error("extracted rg content mismatch");
439
+ }
440
+ });
441
+
442
+ test("extractRgTarGz extracts rg when nested in a subdirectory", (home) => {
443
+ const dest = join(home, "bin");
444
+ fs.mkdirSync(dest, { recursive: true });
445
+ const buf = makeTarGz(home, [
446
+ { name: "ripgrep-x.y.z/rg", content: "#!/bin/sh\necho nested\n", mode: 0o755 },
447
+ ]);
448
+ extractRgTarGz(buf, dest);
449
+ const rg = join(dest, "rg");
450
+ if (!fs.existsSync(rg)) throw new Error("rg was not extracted from nested layout");
451
+ if (fs.readFileSync(rg, "utf-8").indexOf("echo nested") === -1) {
452
+ throw new Error("extracted rg content mismatch");
453
+ }
454
+ });
455
+
456
+ test("extractRgTarGz leaves no temp archive behind on failure", (home) => {
457
+ const dest = join(home, "bin");
458
+ fs.mkdirSync(dest, { recursive: true });
459
+ let threw = false;
460
+ try {
461
+ extractRgTarGz(Buffer.from("not a gzip"), dest);
462
+ } catch {
463
+ threw = true;
464
+ }
465
+ if (!threw) throw new Error("corrupt archive should throw");
466
+ const leftover = fs.readdirSync(dest).filter((f) => f.startsWith("rg-download"));
467
+ if (leftover.length) throw new Error(`temp file leaked: ${leftover.join(", ")}`);
468
+ });
469
+
470
+ test("extractRgTarGz leaves no temp archive behind on success", (home) => {
471
+ const dest = join(home, "bin");
472
+ fs.mkdirSync(dest, { recursive: true });
473
+ const buf = makeTarGz(home, [{ name: "rg", content: "#!/bin/sh\n", mode: 0o755 }]);
474
+ extractRgTarGz(buf, dest);
475
+ const leftover = fs.readdirSync(dest).filter((f) => f.startsWith("rg-download"));
476
+ if (leftover.length) throw new Error(`temp file leaked: ${leftover.join(", ")}`);
477
+ });
478
+
404
479
  runTests();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cc-claw/code",
3
- "version": "0.6.161",
3
+ "version": "0.6.163",
4
4
  "description": "cc-code — Terminal coding agent powered by open-source models — Rust-built, Claude Code compatible",
5
5
  "keywords": [
6
6
  "agent",