diff --git a/bin/bulk-update-apps.mjs b/bin/bulk-update-apps.mjs new file mode 100644 index 000000000..92e084d8b --- /dev/null +++ b/bin/bulk-update-apps.mjs @@ -0,0 +1,114 @@ +#!/usr/bin/env node + +/** + * @file + * You can use this script to bump the version of many apps with the same changes. + * You need to specify a hash for a git commit along with a message that describes the changes made to the apps. + * + * The apps that were changed in the git commit are bumped to a new minor version, + * and their changelogs are updated with the message you provided. + * + * Run it like this: + * node bin/bulk-update-apps.mjs GITHASH CHANGELOGMESSAGE + * + * Example command: + * node bin/exempt-lint.mjs 29ced17i7 'Minor code improvements' + * + * You can also run it in output mode like this: + * node bin/bulk-update-apps.mjs GITHASH --output + * + * This mode doesn't make any changes to your files, it outputs the ID's of all apps that would be bumped. + */ + +import { exec } from "node:child_process"; +import fs from "node:fs/promises"; + +const commitHash = process.argv[2]; +if (!commitHash || commitHash === "--output") { + throw new Error( + "First argument needs to be a git commit hash, something like '29ced17i7'", + ); +} + +const changelogMessage = process.argv[3]; +if (!changelogMessage) { + throw new Error( + "Second argument needs to be a changelog message, something like 'Minor code improvements'", + ); +} + +let outputFlag = false; +if (process.argv[3] === "--output" || process.argv[4] === "--output") { + outputFlag = true; +} + +const gitOutput = await new Promise((resolve) => { + exec( + `git diff-tree --no-commit-id --name-only ${commitHash} -r`, + (error, stdout, stderr) => { + if (error) { + throw new Error(`Could not get git diff: ${error}`); + } else if (stderr) { + throw new Error(`Could not get git diff: ${stderr}`); + } else if (!stdout) { + throw new Error(`Git command did not return any data`); + } + + resolve(stdout); + }, + ); +}); + +/** + * Extract the id of each app and make sure there are no duplicates + */ +const appIds = [ + ...new Set( + [...gitOutput.matchAll(/^(?:.*?\/apps\/|apps\/)(?.*?)\//gmu)] + .map((match) => match?.groups?.id) + .filter((match) => match), + ), +]; + +if (outputFlag) { + for (const appId of appIds) { + console.log(appId); + } +} else { + for (const appId of appIds) { + const metadataPath = `apps/${appId}/metadata.json`; + const changelogPath = `apps/${appId}/ChangeLog`; + + const metadataContent = await fs.readFile(metadataPath, { + encoding: "utf8", + }); + const metadata = JSON.parse(metadataContent); + + const minorVersionNumber = parseInt(metadata.version.split(".").at(-1)); + const newMinorVersionString = `${minorVersionNumber < 9 ? "0" : ""}${(minorVersionNumber + 1).toString()}`; + const newVersion = [ + ...metadata.version.split(".").slice(0, -1), + newMinorVersionString, + ].join("."); + + const versionMatch = metadataContent.match( + /^\s*(?"version"\s*:\s*".*"\s*,)/mu, + ); + const newMatadataContent = metadataContent.replace( + versionMatch.groups.declaration, + `"version": "${newVersion}",`, + ); + await fs.writeFile(metadataPath, newMatadataContent); + + let changelog = await fs.readFile(changelogPath, { + encoding: "utf8", + flag: "a+", + }); + + changelog = changelog.replace(/\n*$/u, ""); //trim trailing whitespace + changelog ||= "0.01: New app!"; // init the changelog file if it doesn't exist + changelog += `\n${newVersion}: ${changelogMessage}\n`; // add the new version with correct whitespace + + await fs.writeFile(changelogPath, changelog, { flag: "w+" }); + } +}