@speedscale/proxymock
Version:
A free desktop CLI that automatically creates mocks and tests from watching your app run.
182 lines (154 loc) • 4.74 kB
JavaScript
import { get } from "https";
import {
createWriteStream,
createReadStream,
readFileSync,
copyFileSync,
unlinkSync,
mkdirSync,
chmodSync,
} from "fs";
import { join } from "path";
import { tmpdir, platform, arch } from "os";
import { createHash } from "crypto";
import logger from "./log.js";
import { getPackageVersion } from "./version.js";
import { INSTALLROOT } from "./constants.js";
async function downloadFile(url, destPath) {
return new Promise((resolve, reject) => {
const file = createWriteStream(destPath);
get(url, (response) => {
if (response.statusCode !== 200) {
reject(new Error(`Failed to download: ${response.statusCode}`));
return;
}
response.pipe(file);
file.on("finish", () => {
file.close(resolve);
});
}).on("error", reject);
});
}
function getPlatformBinary() {
const os = platform();
const architecture = arch();
let osName, archName;
// Map Node.js platform names to our binary names
switch (os) {
case "darwin":
osName = "darwin";
break;
case "linux":
osName = "linux";
break;
case "win32":
return "proxymock.exe"; // Windows binary doesn't have arch suffix
default:
throw new Error(`Unsupported platform: ${os}`);
}
// Map Node.js arch names to our binary names
switch (architecture) {
case "x64":
archName = "amd64";
break;
case "arm64":
archName = "arm64";
break;
default:
throw new Error(`Unsupported architecture: ${architecture}`);
}
return `proxymock-${osName}-${archName}`;
}
async function validateChecksum(filePath, expectedChecksum) {
return new Promise((resolve, reject) => {
const hash = createHash("sha256");
const stream = createReadStream(filePath);
stream.on("data", (data) => {
hash.update(data);
});
stream.on("end", () => {
const actualChecksum = hash.digest("hex");
resolve(actualChecksum === expectedChecksum);
});
stream.on("error", reject);
});
}
export default async function install() {
try {
// Read version from package.json
const npmVersion = getPackageVersion();
// Convert npm version to proxymock version format
// npm: "2.3.660" -> proxymock: "v2.3.660"
const proxymockVersion = `v${npmVersion}`;
const srcfile = getPlatformBinary();
// Build versioned URL
const baseUrl = `https://downloads.speedscale.com/proxymock/${proxymockVersion}`;
const srcUrl = `${baseUrl}/${srcfile}`;
const shaUrl = `${baseUrl}/${srcfile}.sha256`;
// Ensure install directory exists
try {
mkdirSync(INSTALLROOT, { recursive: true });
} catch (error) {
throw new Error(
`Failed to create install directory ${INSTALLROOT}: ${error.message}`,
);
}
// Download checksum first
const tempShaPath = join(tmpdir(), `${srcfile}.sha256`);
await downloadFile(shaUrl, tempShaPath);
const expectedChecksum = readFileSync(tempShaPath, "utf8")
.trim()
.split(" ")[0];
// Download binary
const tempBinPath = join(tmpdir(), srcfile);
await downloadFile(srcUrl, tempBinPath);
// Validate checksum
const isValid = await validateChecksum(tempBinPath, expectedChecksum);
if (!isValid) {
throw new Error("Checksum validation failed");
}
// Install - always as "proxymock" since npm version provides isolation
const dstPath = join(INSTALLROOT, "proxymock");
try {
copyFileSync(tempBinPath, dstPath);
} catch (error) {
throw new Error(
`Failed to install binary to ${dstPath}: ${error.message}`,
);
}
// Make executable on Unix-like systems
if (platform() !== "win32") {
try {
chmodSync(dstPath, "755");
} catch (error) {
throw new Error(`Failed to make binary executable: ${error.message}`);
}
}
// Cleanup temp files (non-fatal if it fails)
try {
unlinkSync(tempShaPath);
} catch (error) {
logger.error(
`Failed to cleanup temp file ${tempShaPath}: ${error.message}`,
);
}
try {
unlinkSync(tempBinPath);
} catch (error) {
logger.error(
`Failed to cleanup temp file ${tempBinPath}: ${error.message}`,
);
}
logger.info(`installed proxymock ${proxymockVersion}`);
} catch (error) {
logger.error(`Installation failed: ${error.message}`);
throw error; // Let the caller handle the error
}
}
// Run installation if this script is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
install().catch((error) => {
logger.error(`Installation failed: ${error.message}`);
process.exit(1);
});
}