mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 12:03:23 +00:00
parent
ef8c950357
commit
3db5715de4
4 changed files with 133 additions and 23 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import chalk from "chalk";
|
||||
import { spawnSync } from "child_process";
|
||||
import { chmodSync, createWriteStream, existsSync, mkdirSync, renameSync, rmSync } from "fs";
|
||||
import extractZip from "extract-zip";
|
||||
import { chmodSync, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync } from "fs";
|
||||
import { arch, platform } from "os";
|
||||
import { join } from "path";
|
||||
import { Readable } from "stream";
|
||||
|
|
@ -130,6 +131,28 @@ async function downloadFile(url: string, dest: string): Promise<void> {
|
|||
await finished(Readable.fromWeb(response.body as any).pipe(fileStream));
|
||||
}
|
||||
|
||||
function findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {
|
||||
const stack: string[] = [rootDir];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const currentDir = stack.pop();
|
||||
if (!currentDir) continue;
|
||||
|
||||
const entries = readdirSync(currentDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(currentDir, entry.name);
|
||||
if (entry.isFile() && entry.name === binaryFileName) {
|
||||
return fullPath;
|
||||
}
|
||||
if (entry.isDirectory()) {
|
||||
stack.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Download and install a tool
|
||||
async function downloadTool(tool: "fd" | "rg"): Promise<string> {
|
||||
const config = TOOLS[tool];
|
||||
|
|
@ -158,33 +181,42 @@ async function downloadTool(tool: "fd" | "rg"): Promise<string> {
|
|||
// Download
|
||||
await downloadFile(downloadUrl, archivePath);
|
||||
|
||||
// Extract
|
||||
const extractDir = join(TOOLS_DIR, "extract_tmp");
|
||||
// Extract into a unique temp directory. fd and rg downloads can run concurrently
|
||||
// during startup, so sharing a fixed directory causes races.
|
||||
const extractDir = join(
|
||||
TOOLS_DIR,
|
||||
`extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
|
||||
);
|
||||
mkdirSync(extractDir, { recursive: true });
|
||||
|
||||
try {
|
||||
// Use tar for both .tar.gz and .zip extraction. Windows 10+ ships bsdtar
|
||||
// which handles both formats, avoiding the need for `unzip` (not available
|
||||
// on Windows by default).
|
||||
const extractResult = assetName.endsWith(".tar.gz")
|
||||
? spawnSync("tar", ["xzf", archivePath, "-C", extractDir], { stdio: "pipe" })
|
||||
: assetName.endsWith(".zip")
|
||||
? spawnSync("tar", ["xf", archivePath, "-C", extractDir], { stdio: "pipe" })
|
||||
: null;
|
||||
|
||||
if (!extractResult || extractResult.error || extractResult.status !== 0) {
|
||||
const errMsg = extractResult?.error?.message ?? extractResult?.stderr?.toString().trim() ?? "unknown error";
|
||||
throw new Error(`Failed to extract ${assetName}: ${errMsg}`);
|
||||
if (assetName.endsWith(".tar.gz")) {
|
||||
const extractResult = spawnSync("tar", ["xzf", archivePath, "-C", extractDir], { stdio: "pipe" });
|
||||
if (extractResult.error || extractResult.status !== 0) {
|
||||
const errMsg = extractResult.error?.message ?? extractResult.stderr?.toString().trim() ?? "unknown error";
|
||||
throw new Error(`Failed to extract ${assetName}: ${errMsg}`);
|
||||
}
|
||||
} else if (assetName.endsWith(".zip")) {
|
||||
await extractZip(archivePath, { dir: extractDir });
|
||||
} else {
|
||||
throw new Error(`Unsupported archive format: ${assetName}`);
|
||||
}
|
||||
|
||||
// Find the binary in extracted files
|
||||
// Find the binary in extracted files. Some archives contain files directly
|
||||
// at root, others nest under a versioned subdirectory.
|
||||
const binaryFileName = config.binaryName + binaryExt;
|
||||
const extractedDir = join(extractDir, assetName.replace(/\.(tar\.gz|zip)$/, ""));
|
||||
const extractedBinary = join(extractedDir, config.binaryName + binaryExt);
|
||||
const extractedBinaryCandidates = [join(extractedDir, binaryFileName), join(extractDir, binaryFileName)];
|
||||
let extractedBinary = extractedBinaryCandidates.find((candidate) => existsSync(candidate));
|
||||
|
||||
if (existsSync(extractedBinary)) {
|
||||
if (!extractedBinary) {
|
||||
extractedBinary = findBinaryRecursively(extractDir, binaryFileName) ?? undefined;
|
||||
}
|
||||
|
||||
if (extractedBinary) {
|
||||
renameSync(extractedBinary, binaryPath);
|
||||
} else {
|
||||
throw new Error(`Binary not found in archive: ${extractedBinary}`);
|
||||
throw new Error(`Binary not found in archive: expected ${binaryFileName} under ${extractDir}`);
|
||||
}
|
||||
|
||||
// Make executable (Unix only)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue