fix(plugins): extract archives without system tar

This commit is contained in:
Peter Steinberger
2026-01-12 01:36:13 +00:00
parent f9fe95d182
commit 23a0bf2abe
3 changed files with 74 additions and 9 deletions

View File

@@ -163,6 +163,7 @@
"proper-lockfile": "^4.1.2",
"qrcode-terminal": "^0.12.0",
"sharp": "^0.34.5",
"tar": "^7.5.2",
"tslog": "^4.10.2",
"undici": "^7.18.2",
"ws": "^8.19.0",

43
pnpm-lock.yaml generated
View File

@@ -130,6 +130,9 @@ importers:
sharp:
specifier: ^0.34.5
version: 0.34.5
tar:
specifier: ^7.5.2
version: 7.5.2
tslog:
specifier: ^4.10.2
version: 4.10.2
@@ -755,6 +758,10 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@isaacs/fs-minipass@4.0.1':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
@@ -1653,6 +1660,10 @@ packages:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'}
chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
chromium-bidi@12.0.1:
resolution: {integrity: sha512-fGg+6jr0xjQhzpy5N4ErZxQ4wF7KLEvhGZXD6EgvZKDhu7iOhZXnZhcDxPJDcwTcrD48NPzOCo84RP2lv3Z+Cg==}
peerDependencies:
@@ -2433,6 +2444,10 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
minizlib@3.1.0:
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
engines: {node: '>= 18'}
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
@@ -2980,6 +2995,10 @@ packages:
tailwindcss@4.1.17:
resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
tar@7.5.2:
resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==}
engines: {node: '>=18'}
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
@@ -3247,6 +3266,10 @@ packages:
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yallist@5.0.0:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
yaml@2.8.2:
resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
engines: {node: '>= 14.6'}
@@ -3690,6 +3713,10 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@isaacs/fs-minipass@4.0.1':
dependencies:
minipass: 7.1.2
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
@@ -4684,6 +4711,8 @@ snapshots:
dependencies:
readdirp: 5.0.0
chownr@3.0.0: {}
chromium-bidi@12.0.1(devtools-protocol@0.0.1561482):
dependencies:
devtools-protocol: 0.0.1561482
@@ -5475,6 +5504,10 @@ snapshots:
minipass@7.1.2: {}
minizlib@3.1.0:
dependencies:
minipass: 7.1.2
mitt@3.0.1: {}
mpg123-decoder@1.0.3:
@@ -6107,6 +6140,14 @@ snapshots:
tailwindcss@4.1.17: {}
tar@7.5.2:
dependencies:
'@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0
minipass: 7.1.2
minizlib: 3.1.0
yallist: 5.0.0
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
@@ -6313,6 +6354,8 @@ snapshots:
yallist@4.0.0: {}
yallist@5.0.0: {}
yaml@2.8.2: {}
yargs-parser@20.2.9: {}

View File

@@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import * as tar from "tar";
import { runCommandWithTimeout } from "../process/exec.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
@@ -85,6 +86,27 @@ async function ensureClawdbotExtensions(manifest: PackageManifest) {
return list;
}
async function withTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
label: string,
): Promise<T> {
let timeoutId: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
promise,
new Promise<T>((_, reject) => {
timeoutId = setTimeout(
() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),
timeoutMs,
);
}),
]);
} finally {
if (timeoutId) clearTimeout(timeoutId);
}
}
export async function installPluginFromArchive(params: {
archivePath: string;
extensionsDir?: string;
@@ -109,15 +131,14 @@ export async function installPluginFromArchive(params: {
await fs.mkdir(extractDir, { recursive: true });
logger.info?.(`Extracting ${archivePath}`);
const tarRes = await runCommandWithTimeout(
["tar", "-xzf", archivePath, "-C", extractDir],
{ timeoutMs },
);
if (tarRes.code !== 0) {
return {
ok: false,
error: `failed to extract archive: ${tarRes.stderr.trim() || tarRes.stdout.trim()}`,
};
try {
await withTimeout(
tar.x({ file: archivePath, cwd: extractDir }),
timeoutMs,
"extract archive",
);
} catch (err) {
return { ok: false, error: `failed to extract archive: ${String(err)}` };
}
let packageDir = "";