common/update-cli.js (192 lines of code) (raw):
const fs = require("fs");
const http = require("http");
const https = require("https");
const {createHash} = require("crypto");
const {readFileSync} = require("fs");
const {execSync} = require("child_process");
const path = require("path");
const cliJsonPath = "./cli.json";
const checksumsKtPath = "plugin/src/main/kotlin/org/jetbrains/qodana/Checksums.kt";
const PLATFORMS = ["windows", "linux", "darwin"];
const ARCHS = ["x86_64", "arm64"];
function sha256sum(file) {
const hash = createHash("sha256");
hash.update(readFileSync(file));
return hash.digest("hex");
}
function downloadFile(url, destinationPath) {
return new Promise((resolve, reject) => {
const protocol = url.startsWith("https") ? https : http;
protocol.get(url, response => {
if (response.statusCode === 200) {
const file = fs.createWriteStream(destinationPath);
response.pipe(file);
file.on("finish", () => {
file.close(resolve);
});
} else if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
downloadFile(response.headers.location, destinationPath)
.then(resolve)
.catch(reject);
} else {
reject(new Error(`Failed to download file. Status code: ${response.statusCode}`));
}
}).on("error", err => {
fs.unlink(destinationPath, () => {
});
reject(err);
});
});
}
function makeRequest(options) {
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
if (res.statusCode >= 200 && res.statusCode <= 299) {
resolve(JSON.parse(data));
} else {
reject(new Error(`Request failed with status code ${res.statusCode}`));
}
});
});
req.on("error", (error) => {
reject(error);
});
req.end();
});
}
async function getLatestRelease() {
try {
const options = {
hostname: "api.github.com",
path: `/repos/jetbrains/qodana-cli/releases/latest`,
headers: {
"User-Agent": "request",
"Accept": "application/vnd.github.v3+json"
}
};
const release = await makeRequest(options);
return release.tag_name.substring(1);
} catch (error) {
console.error("An error occurred:", error);
}
}
function updateCliChecksums(latestVersion, checksumsPath, cliJsonPath) {
const cliJson = JSON.parse(fs.readFileSync(cliJsonPath, "utf-8"));
const allowedKeysfromCliJson = ["windows_x86_64", "windows_arm64", "linux_x86_64", "linux_arm64", "darwin_x86_64", "darwin_arm64"];
const checksums = fs.readFileSync(checksumsPath, "utf-8");
checksums.split("\n").forEach(line => {
const [checksum, filename] = line.trim().split(" ");
if (checksum && filename) {
const key = filename.split("_").slice(1).join("_").split(".")[0];
if (allowedKeysfromCliJson.includes(key)) {
cliJson.checksum[key] = checksum;
}
}
});
cliJson.version = latestVersion;
fs.writeFileSync(cliJsonPath, JSON.stringify(cliJson, null, 2));
fs.unlinkSync(checksumsPath);
}
function updateCircleCIChecksums(circleCIConfigPath) {
let circleCIConfig = fs.readFileSync(circleCIConfigPath, "utf-8");
execSync("curl -fSsL https://github.com/jetbrains/qodana-cli/releases/latest/download/qodana_linux_x86_64.tar.gz -o qodana_linux_x86_64.tar.gz");
execSync("mkdir qodana && tar -xzf qodana_linux_x86_64.tar.gz -C qodana");
const checksum = sha256sum("qodana/qodana");
const circleCIConfigLines = circleCIConfig.split("\n");
circleCIConfigLines[55] = ` QODANA_SHA_256=${checksum}`;
circleCIConfig = circleCIConfigLines.join("\n");
fs.writeFileSync(circleCIConfigPath, circleCIConfig);
execSync("rm -rf qodana/ qodana_linux_x86_64.tar.gz");
}
function updateChecksumsKtFile(checksums, latestVersion) {
const filepath = path.resolve(__dirname, `../${checksumsKtPath}`);
if (fs.existsSync(filepath)) {
console.log(`Checksums.kt file exists at ${filepath}`);
} else {
throw new Error(`Checksums.kt file not found at ${filepath}`);
}
let checksumsKtContent = fs.readFileSync(filepath, 'utf-8');
console.log('Current Checksum File Content:', checksumsKtContent);
const checksumsMapStart = checksumsKtContent.indexOf('val CHECKSUMS = mapOf(');
const checksumsMapEnd = checksumsKtContent.lastIndexOf(')') + 1;
const beforeChecksumsMap = checksumsKtContent.substring(0, checksumsMapStart);
const afterChecksumsMap = checksumsKtContent.substring(checksumsMapEnd);
const newChecksums = ` "${latestVersion}" to mapOf(\n` +
checksums.map(
({platform, arch, checksum}) => ` "${platform}_${arch}" to "${checksum}"`
).join(',\n') +
'\n )';
let updatedChecksumsMap = checksumsKtContent.substring(checksumsMapStart, checksumsMapEnd);
// Insert the new checksums before the final closing parenthesis
updatedChecksumsMap = updatedChecksumsMap.replace(/\n\)$/, `,\n${newChecksums}\n)`);
// Combine all parts
const updatedContent = beforeChecksumsMap + updatedChecksumsMap + afterChecksumsMap;
// Write the updated content back to the file
fs.writeFileSync(filepath, updatedContent);
console.log('Checksums.kt file updated');
}
function updateVersions(latestVersion, currentVersion) {
const latestVersions = latestVersion.split(".");
const latestMajor = parseInt(latestVersions[0]);
const latestMinor = parseInt(latestVersions[1]);
const latestPatch = parseInt(latestVersions[2]);
let taskJson = JSON.parse(
fs.readFileSync(
path.join(__dirname, "..", "vsts", "QodanaScan", "task.json"),
"utf8"
)
);
taskJson.version.Major = latestMajor;
taskJson.version.Minor = latestMinor;
taskJson.version.Patch = latestPatch;
fs.writeFileSync(
path.join(__dirname, "..", "vsts", "QodanaScan", "task.json"),
JSON.stringify(taskJson, null, 2)
);
const currentVersions = currentVersion.split(".");
const currentMajor = parseInt(currentVersions[0]);
const currentMinor = parseInt(currentVersions[1]);
const currentPatch = parseInt(currentVersions[2]);
replaceStringsInProject(`${latestMajor}.${latestMinor}.${latestPatch}`, `${currentMajor}.${currentMinor}.${currentPatch}`);
replaceStringsInProject(`${latestMajor}.${latestMinor}`, `${currentMajor}.${currentMinor}`);
replaceStringsInProject(`${latestMajor}`, `${currentMajor}`);
}
function replaceStringsInProject(newString, oldString) {
process.env.LC_ALL = "C";
const isMacOS = process.platform === "darwin";
const command = `cd .. && find . -type f -not -path "./${checksumsKtPath}" -exec sed -i${isMacOS ? " ''" : ""} 's/${oldString}/${newString}/g' {} +`;
console.log("Running command:", command);
execSync(command, {shell: "/bin/bash"});
}
async function main() {
try {
const currentVersion = JSON.parse(fs.readFileSync(cliJsonPath, "utf-8")).version;
console.log("Current version:", currentVersion);
const latestVersion = await getLatestRelease();
console.log("Latest version:", latestVersion);
console.log("Downloading new checksums...");
await downloadFile(`https://github.com/jetbrains/qodana-cli/releases/latest/download/checksums.txt`, "checksums.txt");
updateVersions(latestVersion, currentVersion);
updateCliChecksums(latestVersion, "checksums.txt", cliJsonPath);
updateCircleCIChecksums("../orb/commands/scan.yml");
// Download binaries, calculate checksums, and update Checksums.kt
const checksums = [];
for (const platform of PLATFORMS) {
for (const arch of ARCHS) {
const url = `https://github.com/jetbrains/qodana-cli/releases/latest/download/qodana_${platform}_${arch}${platform === "windows" ? ".exe" : ""}`;
const filePath = path.join(__dirname, `qodana_${platform}_${arch}${platform === "windows" ? ".exe" : ""}`);
console.log(`Downloading ${url}...`);
await downloadFile(url, filePath);
const checksum = sha256sum(filePath);
checksums.push({platform, arch, checksum});
fs.unlinkSync(filePath);
}
}
updateChecksumsKtFile(checksums, latestVersion);
console.log("Checksums updated successfully");
} catch (error) {
console.error(error);
}
}
main();