Add ultimate installer for Windows

This commit is contained in:
Sander Declerck 2026-01-19 12:47:57 +01:00
parent b1fa9f5492
commit dba101daa7
No known key found for this signature in database
2 changed files with 121 additions and 12 deletions

View file

@ -16,6 +16,7 @@ import path from "path";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import fs from "fs"; import fs from "fs";
import { knownAikidoTools } from "../src/shell-integration/helpers.js"; import { knownAikidoTools } from "../src/shell-integration/helpers.js";
import { installUltimate } from "../src/installation/installUltimate.js";
/** @type {string} */ /** @type {string} */
// This checks the current file's dirname in a way that's compatible with: // This checks the current file's dirname in a way that's compatible with:
@ -62,6 +63,8 @@ if (tool) {
process.exit(0); process.exit(0);
} else if (command === "setup") { } else if (command === "setup") {
setup(); setup();
} else if (command === "--ultimate") {
installUltimate();
} else if (command === "teardown") { } else if (command === "teardown") {
teardownDirectories(); teardownDirectories();
teardown(); teardown();
@ -82,36 +85,41 @@ if (tool) {
function writeHelp() { function writeHelp() {
ui.writeInformation( ui.writeInformation(
chalk.bold("Usage: ") + chalk.cyan("safe-chain <command>") chalk.bold("Usage: ") + chalk.cyan("safe-chain <command>"),
); );
ui.emptyLine(); ui.emptyLine();
ui.writeInformation( ui.writeInformation(
`Available commands: ${chalk.cyan("setup")}, ${chalk.cyan( `Available commands: ${chalk.cyan("setup")}, ${chalk.cyan(
"teardown" "teardown",
)}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("help")}, ${chalk.cyan( )}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("help")}, ${chalk.cyan(
"--version" "--version",
)}` )}`,
); );
ui.emptyLine(); ui.emptyLine();
ui.writeInformation( ui.writeInformation(
`- ${chalk.cyan( `- ${chalk.cyan(
"safe-chain setup" "safe-chain setup",
)}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.` )}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.`,
); );
ui.writeInformation( ui.writeInformation(
`- ${chalk.cyan( `- ${chalk.cyan(
"safe-chain teardown" "safe-chain --ultimate",
)}: This will remove safe-chain aliases from your shell configuration.` )}: This installs the ultimate version of safe-chain, enabling protection for more eco-systems (vscode).`,
); );
ui.writeInformation( ui.writeInformation(
`- ${chalk.cyan( `- ${chalk.cyan(
"safe-chain setup-ci" "safe-chain teardown",
)}: This will setup safe-chain for CI environments by creating shims and modifying the PATH.` )}: 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( ui.writeInformation(
`- ${chalk.cyan("safe-chain --version")} (or ${chalk.cyan( `- ${chalk.cyan("safe-chain --version")} (or ${chalk.cyan(
"-v" "-v",
)}): Display the current version of safe-chain.` )}): Display the current version of safe-chain.`,
); );
ui.emptyLine(); ui.emptyLine();
} }

View file

@ -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
}
}