diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index 841ccee..e33ad9f 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -16,6 +16,7 @@ import path from "path"; import { fileURLToPath } from "url"; import fs from "fs"; import { knownAikidoTools } from "../src/shell-integration/helpers.js"; +import { installUltimate } from "../src/installation/installUltimate.js"; /** @type {string} */ // This checks the current file's dirname in a way that's compatible with: @@ -62,6 +63,8 @@ if (tool) { process.exit(0); } else if (command === "setup") { setup(); +} else if (command === "--ultimate") { + installUltimate(); } else if (command === "teardown") { teardownDirectories(); teardown(); @@ -82,36 +85,41 @@ if (tool) { function writeHelp() { ui.writeInformation( - chalk.bold("Usage: ") + chalk.cyan("safe-chain ") + chalk.bold("Usage: ") + chalk.cyan("safe-chain "), ); ui.emptyLine(); ui.writeInformation( `Available commands: ${chalk.cyan("setup")}, ${chalk.cyan( - "teardown" + "teardown", )}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("help")}, ${chalk.cyan( - "--version" - )}` + "--version", + )}`, ); ui.emptyLine(); ui.writeInformation( `- ${chalk.cyan( - "safe-chain setup" - )}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.` + "safe-chain setup", + )}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.`, ); ui.writeInformation( `- ${chalk.cyan( - "safe-chain teardown" - )}: This will remove safe-chain aliases from your shell configuration.` + "safe-chain --ultimate", + )}: This installs the ultimate version of safe-chain, enabling protection for more eco-systems (vscode).`, ); ui.writeInformation( `- ${chalk.cyan( - "safe-chain setup-ci" - )}: This will setup safe-chain for CI environments by creating shims and modifying the PATH.` + "safe-chain teardown", + )}: This will remove safe-chain aliases from your shell configuration.`, + ); + ui.writeInformation( + `- ${chalk.cyan( + "safe-chain setup-ci", + )}: This will setup safe-chain for CI environments by creating shims and modifying the PATH.`, ); ui.writeInformation( `- ${chalk.cyan("safe-chain --version")} (or ${chalk.cyan( - "-v" - )}): Display the current version of safe-chain.` + "-v", + )}): Display the current version of safe-chain.`, ); ui.emptyLine(); } diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js new file mode 100644 index 0000000..e1323db --- /dev/null +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -0,0 +1,101 @@ +import { platform, arch, tmpdir } from "os"; +import { createWriteStream, unlinkSync } from "fs"; +import { join } from "path"; +import { execSync } from "child_process"; +import { pipeline } from "stream/promises"; +import fetch from "make-fetch-happen"; +import { ui } from "../environment/userInteraction.js"; + +const ULTIMATE_VERSION = "v0.2.0"; + +export function installUltimate() { + const operatingSystem = platform(); + + if (operatingSystem === "win32") { + installOnWindows(); + } else { + ui.writeInformation( + `${operatingSystem} is not supported yet by safe-chain's ultimate version.`, + ); + } +} + +async function installOnWindows() { + if (!isRunningAsAdmin()) { + ui.writeError("Administrator privileges required."); + ui.writeInformation( + "Please run this command in an elevated terminal (Run as Administrator)." + ); + return; + } + + const architecture = getWindowsArchitecture(); + const downloadUrl = buildDownloadUrl(architecture); + const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); + + ui.writeInformation(`Downloading SafeChain Agent ${ULTIMATE_VERSION} for ${architecture}...`); + ui.writeVerbose(`Download URL: ${downloadUrl}`); + ui.writeVerbose(`Destination: ${msiPath}`); + await downloadFile(downloadUrl, msiPath); + + ui.writeInformation("Installing SafeChain Agent..."); + ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); + runMsiInstaller(msiPath); + + ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); + cleanup(msiPath); + ui.writeInformation("SafeChain Agent installed successfully!"); +} + +function isRunningAsAdmin() { + try { + execSync("net session", { stdio: "ignore" }); + return true; + } catch { + return false; + } +} + +function getWindowsArchitecture() { + const nodeArch = arch(); + if (nodeArch === "x64") return "amd64"; + if (nodeArch === "arm64") return "arm64"; + throw new Error(`Unsupported architecture: ${nodeArch}`); +} + +/** + * @param {string} architecture + */ +function buildDownloadUrl(architecture) { + return `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-windows-${architecture}.msi`; +} + +/** + * @param {string} url + * @param {string} destPath + */ +async function downloadFile(url, destPath) { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Download failed: ${response.statusText}`); + } + await pipeline(response.body, createWriteStream(destPath)); +} + +/** + * @param {string} msiPath + */ +function runMsiInstaller(msiPath) { + execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); +} + +/** + * @param {string} msiPath + */ +function cleanup(msiPath) { + try { + unlinkSync(msiPath); + } catch { + // Ignore cleanup errors + } +}