import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, } from "node:fs"; import path from "node:path"; import { spawnSync } from "node:child_process"; function findZstdDecompressCommand() { const systemZstd = spawnSync("zstd", ["--version"], { encoding: "utf8", shell: process.platform === "win32", }); if (systemZstd.status === 0) { return { kind: "system-zstd", command: "zstd", args: (inputPath, outputPath) => [ "-d", "-f", "-o", outputPath, inputPath, ], }; } const extraPaths = [ "/opt/homebrew/bin/zstd", "/usr/local/bin/zstd", path.join(process.env.ProgramFiles || "C:\\Program Files", "zstd", "zstd.exe"), path.join( process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", "zstd", "zstd.exe", ), ]; for (const candidate of extraPaths) { if (!existsSync(candidate)) { continue; } return { kind: "system-zstd", command: candidate, args: (inputPath, outputPath) => [ "-d", "-f", "-o", outputPath, inputPath, ], }; } return null; } function decompressTarZst(inputPath, outputPath) { const zstd = findZstdDecompressCommand(); if (!zstd) { throw new Error( "expand-windows-installer: zstd not found. Install zstd and ensure it is on PATH.", ); } const resolvedInput = path.resolve(inputPath); const resolvedOutput = path.resolve(outputPath); const args = zstd.args(resolvedInput, resolvedOutput); console.log( `expand-windows-installer: decompressing with ${zstd.command} (${zstd.kind})`, ); const result = spawnSync(zstd.command, args, { stdio: "inherit", shell: false, windowsHide: true, }); if (result.status !== 0) { throw new Error( `expand-windows-installer: decompression failed (exit ${result.status ?? 1}) using ${zstd.command} ${args.join(" ")}`, ); } } export function expandWindowsAppFromArchive(setupArchivePath, parentDir) { const resolvedArchive = path.resolve(setupArchivePath); if (!existsSync(resolvedArchive)) { throw new Error(`expand-windows-installer: archive not found: ${resolvedArchive}`); } const workDir = path.join(path.resolve(parentDir), ".expanded-app"); const tarPath = path.join(workDir, "app.tar"); rmSync(workDir, { recursive: true, force: true }); mkdirSync(workDir, { recursive: true }); const archiveForDecompress = path.join(workDir, "setup.tar.zst"); cpSync(resolvedArchive, archiveForDecompress); decompressTarZst(archiveForDecompress, tarPath); const extractTar = spawnSync("tar", ["-xf", tarPath, "-C", workDir], { stdio: "inherit", shell: false, windowsHide: true, }); if (extractTar.status !== 0) { rmSync(workDir, { recursive: true, force: true }); throw new Error( `expand-windows-installer: tar extraction failed (exit ${extractTar.status ?? 1})`, ); } const appDir = readdirSync(workDir) .filter((entry) => entry !== "app.tar" && entry !== "__MACOSX") .map((entry) => path.join(workDir, entry)) .find((entryPath) => statSync(entryPath).isDirectory()); if (!appDir) { rmSync(workDir, { recursive: true, force: true }); throw new Error("expand-windows-installer: app folder not found in archive"); } console.log(`expand-windows-installer: expanded ${appDir}`); return appDir; } export function cleanExpandedWindowsApp(parentDir) { const workDir = path.join(path.resolve(parentDir), ".expanded-app"); rmSync(workDir, { recursive: true, force: true }); }