diff --git a/package.json b/package.json index 9f2ce3147..a9a7bdf2a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1de373b74..70669f9e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/src/plugins/install.ts b/src/plugins/install.ts index b16f650ff..dfb5accec 100644 --- a/src/plugins/install.ts +++ b/src/plugins/install.ts @@ -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( + promise: Promise, + timeoutMs: number, + label: string, +): Promise { + let timeoutId: ReturnType | undefined; + try { + return await Promise.race([ + promise, + new Promise((_, 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 = "";