mirror of
https://github.com/tutao/tutanota.git
synced 2025-12-08 06:09:50 +00:00
256 lines
8.6 KiB
JavaScript
Executable file
256 lines
8.6 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
// @ts-check
|
|
|
|
import { Option, program } from "commander"
|
|
import fs from "node:fs"
|
|
import path from "node:path"
|
|
import { $ } from "zx"
|
|
import { calculateClientVersions } from "./versionUtils.js"
|
|
|
|
await program
|
|
.addOption(new Option("-p, --platform <platform>", "version for which platform to bump").choices(["all", "webdesktop", "android", "ios"]).default("all"))
|
|
.action(run)
|
|
.parseAsync(process.argv)
|
|
|
|
/**
|
|
* @typedef {"major" | "minor" | "patch"} Which
|
|
* @typedef {{name: string, directory: string}} Workspace
|
|
*/
|
|
|
|
/**
|
|
* @param params {object}
|
|
* @param params.platform {undefined | "webdesktop" | "android" | "ios" | "all"}
|
|
* @return {Promise<void>}
|
|
*/
|
|
async function run({ platform }) {
|
|
console.log(`bumping version for ${platform ?? "all"}`)
|
|
const { currentVersion, currentVersionString, newVersionString } = await calculateClientVersions()
|
|
await bumpVersionInCargoWorkspace(newVersionString)
|
|
|
|
if (platform === "all" || platform === "webdesktop") {
|
|
await bumpWorkspaces(newVersionString)
|
|
await $`npm version --no-git-tag-version ${newVersionString}`
|
|
|
|
// Need to clean and re-install to make sure that all packages
|
|
// are installed with the correct version. otherwise, npm list
|
|
// from the tutanota-3 postinstall script will complain about
|
|
// invalid installed versions after npm i.
|
|
await fs.promises.rm("./node_modules", { recursive: true })
|
|
await $`npm i`
|
|
}
|
|
|
|
if (platform === "all" || platform === "ios") {
|
|
await bumpIosVersion(newVersionString)
|
|
}
|
|
|
|
if (platform === "all" || platform === "android") {
|
|
await bumpAndroidVersion("app-android/app/build.gradle.kts")
|
|
await bumpAndroidVersion("app-android/calendar/build.gradle.kts")
|
|
await bumpAndroidVersionName(currentVersion, newVersionString, "app-android/app/build.gradle.kts")
|
|
await bumpAndroidVersionName(currentVersion, newVersionString, "app-android/calendar/build.gradle.kts")
|
|
}
|
|
|
|
console.log(`Bumped version ${currentVersionString} -> ${newVersionString}`)
|
|
}
|
|
|
|
/**
|
|
* @param newVersionString {string}
|
|
* @return {Promise<void>}
|
|
*/
|
|
async function bumpVersionInCargoWorkspace(newVersionString) {
|
|
const workspaceFilePath = "Cargo.toml"
|
|
const versionRegex = /\[workspace\.package]\nversion = ".*"/
|
|
const contents = await fs.promises.readFile(workspaceFilePath, "utf8")
|
|
let found = 0
|
|
const newContents = contents.replace(versionRegex, (_, __, ___, ____) => {
|
|
found += 1
|
|
return `[workspace.package]\nversion = "${newVersionString}"`
|
|
})
|
|
|
|
if (found !== 1) {
|
|
console.warn(`${workspaceFilePath} had an unexpected format and couldn't be updated. Is it corrupted?`)
|
|
} else {
|
|
console.log(`rust: Updated ${workspaceFilePath} package.version to ${newVersionString}`)
|
|
await fs.promises.writeFile(workspaceFilePath, newContents)
|
|
// regenerate the lockfile
|
|
await $`cargo update tuta-sdk tutao_node-mimimi && cargo check --all`
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param newVersionString {string}
|
|
*/
|
|
async function bumpIosVersion(newVersionString) {
|
|
const plists = [
|
|
"app-ios/calendar/Info.plist",
|
|
"app-ios/tutanota/Info.plist",
|
|
"app-ios/TutanotaNotificationExtension/Info.plist",
|
|
"app-ios/tutanotaTests/Info.plist",
|
|
]
|
|
|
|
for (const plist of plists) {
|
|
await replaceCfBundleVersion(plist, newVersionString)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} filePath
|
|
*/
|
|
async function replaceCfBundleVersion(filePath, newVersionString) {
|
|
const infoPlistContents = await fs.promises.readFile(filePath, "utf8")
|
|
let found = 0
|
|
const newInfoPlistContents = infoPlistContents.replaceAll(
|
|
/<key>CFBundle(Short)?Version(String)?<\/key>\s+<string>(\d+\.\d+\.\d+)<\/string>/g,
|
|
(match, _, __, version) => {
|
|
found += 1
|
|
return match.replace(version, newVersionString)
|
|
},
|
|
)
|
|
|
|
if (found !== 2) {
|
|
console.warn(`${filePath} had an unexpected format and couldn't be updated. Is it corrupted?`)
|
|
} else {
|
|
console.log(`iOS: Updated ${filePath} to ${newVersionString}`)
|
|
await fs.promises.writeFile(filePath, newInfoPlistContents)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param buildGradlePath {string}
|
|
* @return {Promise<void>}
|
|
*/
|
|
async function bumpAndroidVersion(buildGradlePath) {
|
|
const buildGradleString = await fs.promises.readFile(buildGradlePath, "utf8")
|
|
|
|
const kotlinRegex = /versionCode = (\d+)/
|
|
const groovyRegex = /versionCode (\d+)/
|
|
|
|
const versionRegex = new RegExp(buildGradlePath.endsWith("kts") ? kotlinRegex : groovyRegex)
|
|
const oldVersionCodeMatch = buildGradleString.match(versionRegex)
|
|
if (oldVersionCodeMatch == null) {
|
|
throw new Error(`Android: Could not find versionCode in ${buildGradlePath}! Is it corrupted?`)
|
|
}
|
|
|
|
const oldVersionCodeString = oldVersionCodeMatch[1]
|
|
const oldVersionCode = parseInt(oldVersionCodeString, 10)
|
|
if (Number.isNaN(oldVersionCode)) {
|
|
throw new Error(`Android: Detected version code as ${oldVersionCodeMatch[1]} but it is not a number! Is it corrupted`)
|
|
}
|
|
|
|
const newVersionCode = oldVersionCode + 1
|
|
const newVersionCodeString = String(newVersionCode)
|
|
|
|
const versionCodeToWrite = `versionCode ${buildGradlePath.endsWith("kts") ? "= " : ""}${newVersionCodeString}`
|
|
const newBuildGradleString = buildGradleString.replace(new RegExp(versionRegex), versionCodeToWrite)
|
|
console.log(`Bumped Android versionCode: ${oldVersionCodeString} -> ${newVersionCodeString} (${buildGradlePath})`)
|
|
await fs.promises.writeFile(buildGradlePath, newBuildGradleString)
|
|
}
|
|
|
|
/**
|
|
* @param currentVersion {number[]}
|
|
* @param newVersionString {string}
|
|
* @param buildGradlePath {string}
|
|
* @return {Promise<void>}
|
|
*/
|
|
async function bumpAndroidVersionName(currentVersion, newVersionString, buildGradlePath) {
|
|
const buildGradleString = await fs.promises.readFile(buildGradlePath, "utf8")
|
|
const newBuildGradleString = buildGradleString.replace(new RegExp(currentVersion.join("\\.")), newVersionString)
|
|
console.log(`Bumped Android versionName: ${currentVersion.join("\\.")} -> ${newVersionString} (${buildGradlePath})`)
|
|
await fs.promises.writeFile(buildGradlePath, newBuildGradleString)
|
|
}
|
|
|
|
/**
|
|
* @return {string[]}
|
|
*/
|
|
function getWorkspaceDirs() {
|
|
const packagesDir = path.resolve("./packages")
|
|
const relativePaths = fs.readdirSync(packagesDir)
|
|
return relativePaths.map((relativePath) => path.join(packagesDir, relativePath))
|
|
}
|
|
|
|
/**
|
|
* @return {Promise<Workspace[]>}
|
|
*/
|
|
async function getWorkspaces() {
|
|
const workspaces = []
|
|
const workspaceDirs = getWorkspaceDirs()
|
|
for (let workspaceDir of workspaceDirs) {
|
|
const packageJson = await readPackageJsonFromDir(workspaceDir)
|
|
workspaces.push({
|
|
name: packageJson.name,
|
|
directory: workspaceDir,
|
|
})
|
|
}
|
|
return workspaces
|
|
}
|
|
|
|
/**
|
|
* @param version {string}
|
|
* @return {Promise<void>}
|
|
*/
|
|
async function bumpWorkspaces(version) {
|
|
// if we don't first bump versions of packages and _then_
|
|
// update the inter-package dependencies we will get an error
|
|
// in the middle of the process because npm can't resolve something
|
|
const workspaces = await getWorkspaces()
|
|
for (const workspace of workspaces) {
|
|
await bumpWorkspaceVersion(version, workspace)
|
|
}
|
|
|
|
for (const workspace of workspaces) {
|
|
const dependency = workspace.name
|
|
await updateDependencyForWorkspaces(version, dependency, workspaces)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param version {string}
|
|
* @param workspace {Workspace}
|
|
* @return {Promise<void>}
|
|
*/
|
|
async function bumpWorkspaceVersion(version, workspace) {
|
|
// set --workspaces-update to false to not run install (it's slow, doesn't work sometimes and we don't need it)
|
|
await $`npm version --workspaces-update false --no-git-tag-version --workspace=${workspace.name} ${version}`
|
|
}
|
|
|
|
/**
|
|
* @param directory {string}
|
|
* @return {Promise<any>}
|
|
*/
|
|
async function readPackageJsonFromDir(directory) {
|
|
const packageJsonPath = path.join(directory, "package.json")
|
|
const packageJsonContents = await fs.promises.readFile(packageJsonPath, { encoding: "utf8" })
|
|
return JSON.parse(packageJsonContents)
|
|
}
|
|
|
|
/**
|
|
* @param version {string}
|
|
* @param dependency {string}
|
|
* @param workspaces {Workspace[]}
|
|
* @return {Promise<void>}
|
|
*/
|
|
async function updateDependencyForWorkspaces(version, dependency, workspaces) {
|
|
await updateDependency({ version, dependency, directory: "." })
|
|
for (const workspace of workspaces) {
|
|
const directory = workspace.directory
|
|
await updateDependency({ version, dependency, directory })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {{version: string, dependency: string, directory: string}} params
|
|
* @return {Promise<void>}
|
|
*/
|
|
async function updateDependency({ version, dependency, directory }) {
|
|
const packageJson = await readPackageJsonFromDir(directory)
|
|
|
|
if (packageJson.dependencies && dependency in packageJson.dependencies) {
|
|
packageJson.dependencies[dependency] = version
|
|
}
|
|
|
|
if (packageJson.devDependencies && dependency in packageJson.devDependencies) {
|
|
packageJson.devDependencies[dependency] = version
|
|
}
|
|
|
|
await fs.promises.writeFile(path.join(directory, "package.json"), JSON.stringify(packageJson, null, "\t"), { encoding: "utf8" })
|
|
}
|