@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.29 → 0.1.1-alpha.30

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.
Files changed (2) hide show
  1. package/dist/index.cjs +77 -4
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -1458,6 +1458,79 @@ function execCaptureErr(cmd) {
1458
1458
  function shellQuote(s) {
1459
1459
  return `'${s.replace(/'/g, `'\\''`)}'`;
1460
1460
  }
1461
+ /**
1462
+ * Extract an npm-packed gzipped tarball.
1463
+ *
1464
+ * ## The problem this works around
1465
+ *
1466
+ * Some tarballs (openclaw's among them — they're not packed by vanilla
1467
+ * `npm pack`) include relative symlinks inside nested .bin/ dirs whose
1468
+ * targets contain `..`, e.g.
1469
+ * node_modules/<pkg>/node_modules/.bin/foo -> ../foo/bin/cli.js
1470
+ *
1471
+ * GNU tar classifies any symlink target with `..` or a leading `/` as
1472
+ * "dangerous" and defers its extraction to a post-files pass, while also
1473
+ * needing a post-files pass to restore directory permissions/mtimes. The
1474
+ * two passes race: the deferred-symlink handling mutates parent-dir inodes,
1475
+ * then the directory stat-restore pass does `fstatat()` and the recorded
1476
+ * inode doesn't match, firing
1477
+ *
1478
+ * tar: <path>: Directory renamed before its status could be extracted
1479
+ *
1480
+ * from `apply_nonancestor_delayed_set_stat()` in extract.c. This is an
1481
+ * `ERROR` (hard-fail, exit 2) — the `--warning=no-rename-directory`
1482
+ * keyword controls a different, incremental-archive code path and does
1483
+ * NOT silence this. Reference: Paul Eggert, bug-tar 2004-04:
1484
+ * https://lists.gnu.org/archive/html/bug-tar/2004-04/msg00021.html
1485
+ *
1486
+ * ## The fix
1487
+ *
1488
+ * Pass `--absolute-names` (aka `-P`). Per GNU tar docs, this disables the
1489
+ * "normalize dangerous names" logic — including the deferred-symlink pass
1490
+ * that's racing us. Also stops stripping leading `/`, but our tarballs
1491
+ * only contain relative (`./node_modules/...`) paths so there's nothing
1492
+ * to strip. Safe because:
1493
+ * - The tarball is sha512-verified upstream (downloadWithCache)
1494
+ * - All entry paths are relative, no absolute-path escape risk
1495
+ * - All dangerous symlink targets resolve within the extracted tree
1496
+ *
1497
+ * ## Belt-and-suspenders
1498
+ *
1499
+ * If some tar variant still emits the error despite -P, we fall through
1500
+ * to checking the stderr pattern: if every error line is the benign
1501
+ * "Directory renamed …" text (no real failures like ENOSPC/EACCES/gzip
1502
+ * CRC/etc.), swallow exit 2. Callers MUST still verify extraction
1503
+ * (e.g. `fs.existsSync(path.join(dest, 'package.json'))`) — tar's
1504
+ * `skip_this_one = 1` after the error means some dirs missed their
1505
+ * mtime/mode restore, but content was written.
1506
+ */
1507
+ function extractTarballTolerant(tarball, destDir, opts = {}) {
1508
+ const strip = opts.stripComponents ?? 0;
1509
+ const stripFlag = strip > 0 ? ` --strip-components=${strip}` : "";
1510
+ const cmd = `tar -xzf ${shellQuote(tarball)} -C ${shellQuote(destDir)}${stripFlag} -P`;
1511
+ try {
1512
+ execCaptureErr(cmd);
1513
+ return;
1514
+ } catch (e) {
1515
+ const msg = e?.message ?? "";
1516
+ const hasFalseAlarm = msg.includes("Directory renamed before its status could be extracted");
1517
+ const hasFatal = [
1518
+ /Cannot open/i,
1519
+ /Cannot mkdir/i,
1520
+ /Cannot create/i,
1521
+ /No space left on device/i,
1522
+ /Disk quota exceeded/i,
1523
+ /Permission denied/i,
1524
+ /Read-only file system/i,
1525
+ /unrecognized option/i,
1526
+ /gzip:/i,
1527
+ /Unexpected EOF/i,
1528
+ /Invalid argument/i
1529
+ ].some((r) => r.test(msg));
1530
+ if (!hasFalseAlarm || hasFatal) throw e;
1531
+ console.error(`[tar] -P did not suppress "Directory renamed" on ${tarball}; tolerating (content must be verified by caller)`);
1532
+ }
1533
+ }
1461
1534
  //#endregion
1462
1535
  //#region src/reset-async.ts
1463
1536
  /**
@@ -1590,7 +1663,7 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
1590
1663
  node_fs.default.mkdirSync(node_path.default.dirname(targetDir), { recursive: true });
1591
1664
  const tmpStage = node_fs.default.mkdtempSync(node_path.default.join(opts.tmpRoot ?? node_os.default.tmpdir(), "openclaw-install-"));
1592
1665
  try {
1593
- execCaptureErr(`tar -xzf '${tarball}' -C '${tmpStage}' --strip-components=1`);
1666
+ extractTarballTolerant(tarball, tmpStage, { stripComponents: 1 });
1594
1667
  if (!node_fs.default.existsSync(node_path.default.join(tmpStage, "package.json"))) throw new Error("extracted tarball missing package.json");
1595
1668
  moveSafe(tmpStage, newDir);
1596
1669
  const hadExisting = node_fs.default.existsSync(targetDir);
@@ -1703,7 +1776,7 @@ function installOne(pkg, tarball, homeBase) {
1703
1776
  });
1704
1777
  node_fs.default.mkdirSync(stagingDir);
1705
1778
  try {
1706
- execCaptureErr(`tar -xzf '${tarball}' -C '${stagingDir}' --strip-components=1`);
1779
+ extractTarballTolerant(tarball, stagingDir, { stripComponents: 1 });
1707
1780
  if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
1708
1781
  } catch (e) {
1709
1782
  try {
@@ -1741,7 +1814,7 @@ async function downloadResource(tag, ossFileMap, opts) {
1741
1814
  const format = (pkg.format ?? "").toLowerCase();
1742
1815
  const lower = pkg.ossKey.toLowerCase();
1743
1816
  if (format === "tgz" || lower.endsWith(".tgz") || lower.endsWith(".tar.gz")) {
1744
- execCaptureErr(`tar -xzf '${file}' -C '${extractDir}'`);
1817
+ extractTarballTolerant(file, extractDir);
1745
1818
  console.error(`[download-resource] ${opts.role}/${opts.name}: extracted to ${extractDir}`);
1746
1819
  } else {
1747
1820
  const basename = node_path.default.posix.basename(pkg.ossKey);
@@ -2520,7 +2593,7 @@ async function runDoctor(rawCtx, opts) {
2520
2593
  //#region src/help.ts
2521
2594
  const BIN = "mclaw-diagnose";
2522
2595
  function versionBanner() {
2523
- return `v0.1.1-alpha.29`;
2596
+ return `v0.1.1-alpha.30`;
2524
2597
  }
2525
2598
  const COMMANDS = [
2526
2599
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.1-alpha.29",
3
+ "version": "0.1.1-alpha.30",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {