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();