isaacscript
Version:
A command line tool for managing Isaac mods written in TypeScript.
162 lines • 7.67 kB
JavaScript
import { Command } from "@commander-js/extra-typings";
import { fatalError, getPackageJSONField, getPackageJSONVersion, getPackageManagerInstallCommand, isFile, readFile, updatePackageJSONDependencies, writeFile, } from "complete-node";
import { CONSTANTS_TS_PATH, CWD, METADATA_XML_PATH, VERSION_TXT_PATH, } from "../../constants.js";
import { execShell, execShellString } from "../../exec.js";
import { getReleaseGitCommitMessage, gitCommitAllAndPush } from "../../git.js";
import { getPackageManagerUsedForExistingProject } from "../../packageManager.js";
import { getIsaacScriptModMissingFile } from "../../validateMod.js";
import { publishIsaacScriptMod } from "./isaacscriptMod.js";
import { validate } from "./validate.js";
export const publishCommand = new Command()
.command("publish")
.description("Bump the version & publish a new release.")
.allowExcessArguments(false) // By default, Commander.js will allow extra positional arguments.
.helpOption("-h, --help", "Display the list of options for this command.")
.option("--major", "Perform a major version bump.", false)
.option("--minor", "Perform a minor version bump.", false)
.option("--patch", "Perform a patch version bump.", false)
.option("--skip-increment", "Skip incrementing the version number.", false)
.option("--set-version <version>", "Specify the version number instead of incrementing it.")
.option("--dry-run", "Skip committing/uploading & perform a Git reset afterward.", false)
.option("--skip-update", "Skip updating the npm dependencies.", false)
.option("--skip-lint", "Skip linting before publishing.", false)
.option("-v, --verbose", "Enable verbose output.", false)
.action(async (options) => {
await publish(options);
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const publishOptions = publishCommand.opts();
async function publish(options) {
const { dryRun, setVersion, verbose } = options;
const isaacScriptModMissingFile = await getIsaacScriptModMissingFile(CWD);
const typeScript = isaacScriptModMissingFile !== undefined;
await validate(typeScript, setVersion, verbose);
await prePublish(options, typeScript);
await (typeScript
? publishTypeScriptProject(dryRun, verbose)
: publishIsaacScriptMod(dryRun, verbose));
}
/**
* Before uploading the project, we want to update dependencies, increment the version, and perform
* some other steps.
*/
async function prePublish(options, typeScript) {
const { dryRun, skipLint, skipUpdate, verbose } = options;
const packageManager = await getPackageManagerUsedForExistingProject();
execShellString("git pull --rebase");
execShellString("git push");
await updateDependencies(skipUpdate, dryRun, packageManager, verbose);
await incrementVersion(options, typeScript);
await unsetDevelopmentConstants();
tryRunNPMScript("build", verbose);
if (!skipLint) {
tryRunNPMScript("lint", verbose);
}
}
async function updateDependencies(skipUpdate, dryRun, packageManager, verbose) {
if (skipUpdate) {
return;
}
console.log('Updating dependencies in the "package.json" file...');
const hasNewDependencies = await updatePackageJSONDependencies(undefined);
if (hasNewDependencies) {
const command = getPackageManagerInstallCommand(packageManager);
execShellString(command, verbose);
if (!dryRun) {
gitCommitAllAndPush("chore: update dependencies", verbose);
}
}
}
async function incrementVersion(options, typeScript) {
const { skipIncrement, verbose } = options;
if (skipIncrement) {
return;
}
const versionCommandArgument = getVersionCommandArgument(options);
// We always use `npm` here to avoid differences with the version command between package
// managers. The "--no-git-tag-version" flag will prevent npm from both making a commit and adding
// a tag.
execShell("npm", ["version", versionCommandArgument, "--no-git-tag-version"], verbose);
if (!typeScript) {
const version = await getPackageJSONVersion(undefined);
await writeVersionToConstantsTS(version);
await writeVersionToMetadataXML(version);
await writeVersionToVersionTXT(version);
}
}
function getVersionCommandArgument(options) {
if (options.setVersion !== undefined) {
// They want to use a specific version, which was manually specified.
return options.setVersion;
}
if (options.major) {
return "major";
}
if (options.minor) {
return "minor";
}
if (options.patch) {
return "patch";
}
// Default to a patch version.
return "patch";
}
async function writeVersionToConstantsTS(version) {
const file = await isFile(CONSTANTS_TS_PATH);
if (!file) {
console.log('Skipping writing the version to "constants.ts" since it was not found.');
return;
}
const constantsTS = await readFile(CONSTANTS_TS_PATH);
const newConstantsTS = constantsTS.replace(/const VERSION = ".+"/, `const VERSION = "${version}"`);
await writeFile(CONSTANTS_TS_PATH, newConstantsTS);
console.log(`The version of ${version} was written to: ${CONSTANTS_TS_PATH}`);
}
async function writeVersionToMetadataXML(version) {
const metadataXML = await readFile(METADATA_XML_PATH);
const newMetadataXML = metadataXML.replace(/<version>.+<\/version>/, `<version>${version}</version>`);
await writeFile(METADATA_XML_PATH, newMetadataXML);
console.log(`The version of ${version} was written to: ${METADATA_XML_PATH}`);
}
async function writeVersionToVersionTXT(version) {
await writeFile(VERSION_TXT_PATH, version);
console.log(`The version of ${version} was written to: ${VERSION_TXT_PATH}`);
}
async function unsetDevelopmentConstants() {
const file = await isFile(CONSTANTS_TS_PATH);
if (!file) {
return;
}
const constantsTS = await readFile(CONSTANTS_TS_PATH);
const newConstantsTS = constantsTS
.replace("const IS_DEV = true", "const IS_DEV = false")
.replace("const DEBUG = true", "const DEBUG = false");
await writeFile(CONSTANTS_TS_PATH, newConstantsTS);
}
function tryRunNPMScript(scriptName, verbose) {
console.log(`Running: ${scriptName}`);
const { exitStatus, stdout } = execShell("npm", ["run", scriptName], verbose, true);
if (exitStatus !== 0) {
execShellString("git reset --hard", verbose); // Revert the version changes.
fatalError(`Failed to run "${scriptName}":`, stdout);
}
}
async function publishTypeScriptProject(dryRun, verbose) {
const projectName = await getPackageJSONField(undefined, "name");
const version = await getPackageJSONVersion(undefined);
if (dryRun) {
execShellString("git reset --hard", verbose); // Revert the version changes.
}
else {
const releaseGitCommitMessage = getReleaseGitCommitMessage(version);
gitCommitAllAndPush(releaseGitCommitMessage, verbose);
// - The "--access=public" flag is only technically needed for the first publish (unless the
// package is a scoped package), but it is saved here for posterity.
// - The "--ignore-scripts" flag is needed since the "npm publish" command will run the
// "publish" script in the "package.json" file, causing an infinite loop.
execShellString("npm publish --access=public --ignore-scripts", verbose);
}
const dryRunSuffix = dryRun ? " (dry-run)" : "";
console.log(`Published ${projectName} version ${version} successfully${dryRunSuffix}.`);
}
//# sourceMappingURL=publish.js.map