Path: blob/main/components/ide/gha-update-image/lib/jb-stable-version.ts
2499 views
// Copyright (c) 2024 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34import axios from "axios";5import { $ } from "bun";6import { z } from "zod";7import semver from "semver";8import { MultipleBuildVersionsError, MultipleMajorVersionsError } from "./errors";9import { pathToWorkspaceYaml, pathToBackendPluginGradleStable, readWorkspaceYaml } from "./common";1011$.nothrow(); // git likes to respond with non-zero codes, but it is alright for us1213const JB_PRODUCTS_DATA_URL = "https://data.services.jetbrains.com/products";1415const jbReleaseResponse = z.array(16z.object({17name: z.string(),18link: z.string(),19releases: z.array(20z.object({21majorVersion: z.string(),22build: z.string(),23downloads: z.object({24linux: z25.object({26link: z.string(),27})28.optional(),29}),30}),31),32}),33);3435export interface JetBrainsIDE {36productName: string;37productId: string;38productCode: string;39productType: string;40exampleRepo: string;41}4243export const ides: JetBrainsIDE[] = [44{45productName: "IntelliJ IDEA Ultimate",46productId: "intellij",47productCode: "IIU",48productType: "release",49exampleRepo: "https://github.com/gitpod-samples/spring-petclinic",50},51{52productName: "GoLand",53productId: "goland",54productCode: "GO",55productType: "release",56exampleRepo: "https://github.com/gitpod-samples/template-golang-cli",57},58{59productName: "PyCharm Professional Edition",60productId: "pycharm",61productCode: "PCP",62productType: "release",63exampleRepo: "https://github.com/gitpod-samples/template-python-django",64},65{66productName: "PhpStorm",67productId: "phpstorm",68productCode: "PS",69productType: "release",70exampleRepo: "https://github.com/gitpod-samples/template-php-laravel-mysql",71},72{73productName: "RubyMine",74productId: "rubymine",75productCode: "RM",76productType: "release",77exampleRepo: "https://github.com/gitpod-samples/template-ruby-on-rails-postgres",78},79{80productName: "WebStorm",81productId: "webstorm",82productCode: "WS",83productType: "release",84exampleRepo: "https://github.com/gitpod-samples/template-typescript-react",85},86// TODO(hw): ENT-777 We don't upgrade Rider until we can build backend-plugin for it87// {88// productName: "Rider",89// productId: "rider",90// productCode: "RD",91// productType: "release",92// exampleRepo: "https://github.com/gitpod-samples/template-dotnet-core-cli-csharp",93// },94{95productName: "CLion",96productId: "clion",97productCode: "CL",98productType: "release",99exampleRepo: "https://github.com/gitpod-samples/template-cpp",100},101{102productName: "RustRover",103productId: "rustrover",104productCode: "RR",105productType: "release",106exampleRepo: "https://github.com/gitpod-samples/template-rust-cli",107},108] as const;109110const workspaceYamlInfo = await readWorkspaceYaml();111let rawWorkspace = workspaceYamlInfo.rawText;112const workspace = workspaceYamlInfo.parsedObj;113114export const getStableVersionsInfo = async (ides: JetBrainsIDE[]) => {115let buildVersion: semver.SemVer | undefined;116117const uniqueMajorVersions = new Set<string>();118const uniqueMajorBuildVersions = new Set<string>();119const updatedIDEs: string[] = [];120121await Promise.all(122ides.map(async (ide) => {123const params = new URLSearchParams({124code: ide.productCode,125"release.type": ide.productType,126fields: ["distributions", "link", "name", "releases"].join(","),127_: Date.now().toString(),128});129130const url = new URL(JB_PRODUCTS_DATA_URL);131url.search = params.toString();132console.debug(`Fetching data for ${ide.productId.padStart(8, " ")}: ${url.toString()}`);133const resp = await axios(url.toString());134135const parsedResponse = jbReleaseResponse.parse(resp.data);136const lastRelease = parsedResponse[0].releases[0];137console.log(138`Latest ${ide.productId.padEnd(8, " ")} majorVersion: ${lastRelease.majorVersion}, buildVersion: ${139lastRelease.build140}`,141);142143uniqueMajorVersions.add(lastRelease.majorVersion);144145const ideKey = `${ide.productId}DownloadUrl` as const;146const oldDownloadUrl = workspace.defaultArgs[ideKey];147148const downloadLink = lastRelease.downloads?.linux?.link;149if (!downloadLink) {150throw new Error("No download link found for the latest release");151}152rawWorkspace = rawWorkspace.replace(oldDownloadUrl, downloadLink);153if (oldDownloadUrl !== downloadLink) {154updatedIDEs.push(ide.productId);155}156157const currentBuildVersion = semver.parse(lastRelease.build);158if (!currentBuildVersion) {159throw new Error("Failed to parse the build version: " + lastRelease.build);160}161uniqueMajorBuildVersions.add(currentBuildVersion.major.toString());162// Use minimal common build version, within the same major version there should have no breaking changes163if (!buildVersion || semver.lt(currentBuildVersion, buildVersion)) {164buildVersion = currentBuildVersion;165}166}),167);168169const majorVersions = [...uniqueMajorVersions];170const majorBuildVersions = [...uniqueMajorBuildVersions];171console.log({ majorVersions, majorBuildVersions, buildVersion });172173if (!buildVersion) {174throw new Error("build version is unresolved");175}176if (majorBuildVersions.length !== 1) {177throw new MultipleBuildVersionsError(majorBuildVersions);178}179180if (majorVersions.length !== 1) {181throw new MultipleMajorVersionsError(majorVersions);182}183184const majorVersion = majorVersions[0];185console.log(`All IDEs are in the same major version: ${majorVersion}`);186187return { buildVersion, majorVersion, updatedIDEs };188};189190// TODO: remove me and use jb-gradle-task-config.ts191export const upgradeStableVersionsInWorkspaceaAndGradle = async () => {192try {193const { buildVersion, majorVersion, updatedIDEs } = await getStableVersionsInfo(ides);194await Bun.write(pathToWorkspaceYaml, rawWorkspace);195196await Bun.write(197pathToBackendPluginGradleStable,198`# this file is auto generated by components/ide/gha-update-image/index-jb.ts199# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html200# for insight into build numbers and IntelliJ Platform versions.201# revert pluginSinceBuild if it's unnecessary202pluginSinceBuild=${buildVersion.major}.${buildVersion.minor}203pluginUntilBuild=${buildVersion.major}.*204# Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl205# See https://jb.gg/intellij-platform-builds-list for available build versions.206pluginVerifierIdeVersions=${majorVersion}207# Version from "com.jetbrains.intellij.idea" which can be found at https://www.jetbrains.com/intellij-repository/snapshots208platformVersion=${buildVersion.major}.${buildVersion.minor}-EAP-CANDIDATE-SNAPSHOT209`,210);211212console.log(`File updated: ${pathToWorkspaceYaml}`);213console.log(`File updated: ${pathToBackendPluginGradleStable}`);214return updatedIDEs;215} catch (e) {216if (e instanceof MultipleMajorVersionsError || e instanceof MultipleBuildVersionsError) {217console.error(e.message);218return;219}220throw e;221}222};223224225