Skip to content

Commit

Permalink
Merge branch 'v4.6.0' into byPrototype
Browse files Browse the repository at this point in the history
  • Loading branch information
yofukashino authored Aug 11, 2023
2 parents 12dd911 + 8093dde commit 414ee01
Show file tree
Hide file tree
Showing 10 changed files with 390 additions and 54 deletions.
277 changes: 277 additions & 0 deletions bin/release.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
#!/usr/bin/env node
/* eslint-disable no-process-exit */

import { existsSync, readFileSync, writeFileSync } from "fs";
import path from "path";
import prompts from "prompts";
import semver from "semver";
import chalk from "chalk";
import { execSync } from "child_process";

/**
* Prompt a confirmation message and exit if the user does not confirm.
*
* @param {string} message
* @param {boolean} [initial]
*/
async function confirmOrExit(message, initial = false) {
const { doContinue } = await prompts(
{
type: "confirm",
name: "doContinue",
message: chalk.yellow(message),
initial,
},
{ onCancel },
);

if (!doContinue) {
console.log(chalk.red("Aborting"));
process.exit(0);
}
}

/**
* Run a command and return the output.
*
* @param {string} command
* @param {boolean} [exit = true] Exit if the command fails
* @returns {string}
*/
function runCommand(command, exit = true) {
try {
const result = execSync(command, {
encoding: "utf8",
cwd: getRootDir(),
});
return result;
} catch (error) {
// @ts-expect-error
if (!exit) return error.stdout;
// @ts-expect-error
console.error(error.message);
process.exit(1);
}
throw new Error("Unreachable");
}

function onCancel() {
console.log(chalk.red("Aborting"));
process.exit(128); // SIGINT
}

/** @type {string} */
let root;

function getRootDir() {
if (root) return root;

try {
root = execSync("git rev-parse --show-toplevel", {
encoding: "utf8",
cwd: process.cwd(),
}).trim();
return root;
} catch (error) {
// @ts-expect-error
if (error.message.includes("not a git repository")) {
console.log(chalk.red("You must run this command from within a git repository"));
process.exit(1);
}

// @ts-expect-error
console.error(`Command failed with exit code ${error.status}: ${error.message}`);
process.exit(1);
}

throw new Error("Unreachable");
}

export async function release() {
const directory = getRootDir();

const status = runCommand("git status --porcelain");
const isClean = !status.trim();
if (!isClean) await confirmOrExit("Working directory is not clean. Continue?");

const manifestPath = path.resolve(directory, "manifest.json");
if (!existsSync(manifestPath)) {
console.log(chalk.red("manifest.json not found"));
process.exit(1);
}
const manifestText = readFileSync(manifestPath, "utf8");
let manifest;
try {
manifest = JSON.parse(manifestText);
} catch {
console.log(chalk.red("manifest.json is not valid JSON"));
process.exit(1);
}

const packagePath = path.resolve(directory, "package.json");
if (!existsSync(packagePath)) {
console.log(chalk.red("package.json not found"));
process.exit(1);
}
const packageText = readFileSync(packagePath, "utf8");
let packageJson;
try {
packageJson = JSON.parse(packageText);
} catch {
console.log(chalk.red("package.json is not valid JSON"));
process.exit(1);
}

// Prompt for version

const { version } = manifest;
let nextVersion;

const isValidSemver = Boolean(semver.valid(version));
if (isValidSemver) {
const nextPatch = semver.inc(version, "patch");
const nextMinor = semver.inc(version, "minor");
const nextMajor = semver.inc(version, "major");

({ nextVersion } = await prompts(
{
type: "select",
name: "nextVersion",
message: "Version",
choices: [
{
title: `Patch: v${nextPatch}`,
value: nextPatch,
},
{
title: `Minor: v${nextMinor}`,
value: nextMinor,
},
{
title: `Major: v${nextMajor}`,
value: nextMajor,
},
{
title: "Custom",
value: null,
},
],
},
{ onCancel },
));
}
if (!nextVersion) {
({ nextVersion } = await prompts(
{
type: "text",
name: "nextVersion",
message: isValidSemver ? "Custom Version" : "Version",
validate: (value) => {
if (!value.trim()) return "Version is required";

return true;
},
},
{ onCancel },
));
}

nextVersion = nextVersion.trim();
const isNewValidSemver = Boolean(semver.valid(nextVersion));
if (isValidSemver) {
// If the existing version is not semver, don't bother with semver checks
if (isNewValidSemver) {
if (semver.lte(nextVersion, version)) {
await confirmOrExit(`Version ${nextVersion} is not greater than ${version}. Continue?`);
}
const cleaned = semver.clean(nextVersion);
if (cleaned !== nextVersion) {
let { clean } = await prompts({
type: "confirm",
name: "clean",
message: `Convert ${nextVersion} to cleaned version ${cleaned}?`,
initial: true,
});
if (clean) nextVersion = cleaned;
}
} else {
await confirmOrExit(`Version ${nextVersion} is not a valid semver. Continue?`);
}
}

// Update manifest.json and package.json
manifest.version = nextVersion;
packageJson.version = nextVersion;

// Write manifest.json and package.json (indent with 2 spaces and keep trailing newline)
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);

// Stage changes
runCommand("git add manifest.json package.json");

// Commit changes
const { message } = await prompts(
{
type: "text",
name: "message",
message: "Commit message",
initial: `Release v${nextVersion}`,
validate: (value) => {
if (!value.trim()) return "Commit message is required";

return true;
},
},
{ onCancel },
);

// Pick tag name
const existingTags = runCommand("git tag --list").split("\n").filter(Boolean);

const { tagName } = await prompts(
{
type: "text",
name: "tagName",
message: "Tag name",
initial: `v${nextVersion}`,
validate: (value) => {
if (!value.trim()) return "Tag name is required";

if (existingTags.includes(value)) return `Tag ${value} already exists`;

return true;
},
},
{ onCancel },
);

const hasSigningKey = Boolean(runCommand("git config --get user.signingkey", false).trim());
const commitSigningEnabled =
runCommand("git config --get commit.gpgsign", false).trim() === "true";
const tagSigningEnabled = runCommand("git config --get tag.gpgsign", false).trim() === "true";

let sign = false;
if (hasSigningKey && (!commitSigningEnabled || !tagSigningEnabled)) {
({ sign } = await prompts({
type: "confirm",
name: "sign",
message: "Sign commit and tag?",
initial: true,
}));
}

// Commit changes
runCommand(`git commit${sign ? " -S" : ""} -m "${message}"`);

// Tag commit
runCommand(`git tag${sign ? " -s" : ""} -a -m "${message}" "${tagName}"`);

// Push changes
await confirmOrExit("Push changes to remote?", true);

runCommand("git push");

// And the tag
runCommand("git push --tags");
}
2 changes: 2 additions & 0 deletions bin/replugged.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { hideBin } from "yargs/helpers";
import chalk from "chalk";
import WebSocket from "ws";
import { fileURLToPath, pathToFileURL } from "url";
import { release } from "./release.mjs";

const dirname = path.dirname(fileURLToPath(import.meta.url));
const directory = process.cwd();
Expand Down Expand Up @@ -566,6 +567,7 @@ const { argv } = yargs(hideBin(process.argv))
sendUpdateNotification();
},
)
.command("release", "Interactively release a new version of an addon", () => {}, release)
.parserConfiguration({
"boolean-negation": false,
})
Expand Down
3 changes: 3 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"crosspost",
"customitem",
"dependants",
"devmode",
"discordapp",
"doas",
"dont",
Expand All @@ -29,6 +30,7 @@
"Fonticons",
"getent",
"globstar",
"gpgsign",
"groupstart",
"HighlightJS",
"installdir",
Expand All @@ -55,6 +57,7 @@
"resizeable",
"Scroller",
"shopt",
"signingkey",
"skus",
"uninject",
"uninjector",
Expand Down
4 changes: 3 additions & 1 deletion i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,5 +225,7 @@
"REPLUGGED_SETTINGS_REACT_DEVTOOLS_FAILED": "Ошибка установки React DevTools.",
"REPLUGGED_INSTALLER_OPEN_STORE": "Посмотреть в магазине",
"REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC": "Посмотреть в хранилище.",
"REPLUGGED_SETTINGS_ADDON_EMBEDS": "Показывать карточку с информацией об аддоне, при отправке ссылки на хранилище/установку в чате"
"REPLUGGED_SETTINGS_ADDON_EMBEDS": "Показывать карточку с информацией об аддоне, при отправке ссылки на хранилище/установку в чате",
"REPLUGGED_RESTART": "Перезагрузить",
"REPLUGGED_SETTINGS_RESTART_TITLE": "Требуется перезагрузка"
}
17 changes: 10 additions & 7 deletions i18n/sv-SE.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
"REPLUGGED_UPDATES_OPTS_CHANGE_LOGS": "Öppna Förändrings Logg",
"REPLUGGED_UPDATES_OPTS_CONCURRENCY_DESC": "Hur många samtidiga uppgifter Replugged körs i bakgrunden för att leta efter uppdateringar. Minst 1. Om du är osäker, håll den kvar vid 2.",
"REPLUGGED_UPDATES_OPTS_CONCURRENCY": "Gränsen för samtidiga uppdateringar",
"REPLUGGED_UPDATES_OPTS_INTERVAL_DESC": "Hur ofta Replugged letar efter uppdateringar (i minuter). Minimum 10 minuter.",
"REPLUGGED_UPDATES_OPTS_INTERVAL_DESC": "Hur ofta Replugged letar efter uppdateringar. Minimum 10 minuter.",
"REPLUGGED_UPDATES_OPTS_INTERVAL": "Uppdatering-letnings Interval",
"REPLUGGED_UPDATES_OPTS_AUTO_DESC": "Replugged kan automatiskt ladda ner och installera uppdateringar utan att irritera dig för mycket. Uppdateringar kräver fortfarande användaråtgärder om en omstart krävs eller om det är en konflikt.",
"REPLUGGED_UPDATES_OPTS_AUTO": "Uppdatera automatiskt i bakgrunden",
"REPLUGGED_UPDATES_OPTS_AUTO_DESC": "Replugged kommer att automatiskt kolla efter nya uppdateringar och visa dig en notis när en finns tillgänglig. Uppdateringar kommer inte installeras tills du väljer att installera dem. Endast officiella addons kommer att uppdateras automatiskt.",
"REPLUGGED_UPDATES_OPTS_AUTO": "Uppdatera automatiskt",
"REPLUGGED_UPDATES_FORCE": "Tvinga en uppdatering",
"REPLUGGED_UPDATES_UPDATE": "Uppdatera Nu",
"REPLUGGED_UPDATES_UPDATE": "Uppdatera",
"REPLUGGED_UPDATES_CHECK": "Leta efter Uppdateringar",
"REPLUGGED_UPDATES_ENABLE": "Aktivera Uppdateringar",
"REPLUGGED_UPDATES_LAST_CHECKED": "Senast kollat: {date}",
Expand Down Expand Up @@ -106,15 +106,15 @@
"REPLUGGED_INSTALL_MODAL_HEADER": "Installera {type}",
"REPLUGGED_NOTICES_JOIN_SERVER_BUTTON": "Gå med i server",
"REPLUGGED_PLUGIN": "Plugin",
"REPLUGGED_SETTINGS_ERROR_HEADER": "Huh, det är konstigt",
"REPLUGGED_SETTINGS_ERROR_HEADER": "Något gick fel under renderingen av detta element!",
"REPLUGGED_SETTINGS_ERROR_RENDER_PANEL": "Ett fel uppstod under renderingen av inställningspanelen.",
"REPLUGGED_THEME": "Tema",
"REPLUGGED_UPDATES_OPTS_DEBUG_CATEGORY_PROCESS_VERSIONS": "Processversioner",
"REPLUGGED_UPDATES_OPTS_DEBUG_LOCALE": "Språk:",
"REPLUGGED_UPDATES_OPTS_DEBUG_OS": "OS:",
"REPLUGGED_UPDATES_OPTS_DEBUG_OS_64BIT": "64-bitars",
"REPLUGGED_UPDATES_OPTS_DEBUG_DISTRO": "Distribution:",
"REPLUGGED_UPDATES_OPTS_DEBUG_APP_VERSION": "App Version:",
"REPLUGGED_UPDATES_OPTS_DEBUG_APP_VERSION": "Appens version:",
"REPLUGGED_UPDATES_OPTS_DEBUG_EXPERIMENTS": "Experiment:",
"REPLUGGED_UPDATES_OPTS_DEBUG_COMMANDS": "Kommandon:",
"REPLUGGED_UPDATES_OPTS_DEBUG_COPIED": "Kopierat!",
Expand Down Expand Up @@ -167,5 +167,8 @@
"REPLUGGED_INSTALLER_INSTALL_PROMPT_BODY": "Vill du installera {name}{authors}?",
"REPLUGGED_UPDATES_UPDATE_ALL": "Uppdatera Alla",
"REPLUGGED_UPDATES_UPDATE_TO": "Uppdatera till {version}",
"REPLUGGED_RELOAD": "Ladda om"
"REPLUGGED_RELOAD": "Ladda om",
"REPLUGGED_BADGES_BOOSTER": "Replugged Server Boosts",
"REPLUGGED_ADDON_DELETE": "Radera {type}",
"REPLUGGED_UPDATES_OPTS_TOAST_ENABLED": "Visa uppdateringskontrollsmenyn"
}
Loading

0 comments on commit 414ee01

Please sign in to comment.