diff --git a/src/changelog.ts b/src/changelog.ts index 159eb88..bbf453f 100644 --- a/src/changelog.ts +++ b/src/changelog.ts @@ -2,9 +2,9 @@ import { resolve } from "node:path"; import { constants, accessSync, writeFileSync, readFileSync } from "node:fs"; import conventionalChangelog from "conventional-changelog"; import type { ForkConfigOptions } from "./configuration.js"; -import type { bumpVersion } from "./version.js"; +import type { BumpVersion } from "./version.js"; -function createChangelog(options: ForkConfigOptions) { +function createChangelog(options: ForkConfigOptions): string { const changelogPath = resolve(options.changelog); try { @@ -31,7 +31,7 @@ const RELEASE_PATTERN = /(^#+ \[?[0-9]+\.[0-9]+\.[0-9]+|>, -) { +function getChanges(options: ForkConfigOptions, bumpResult: BumpVersion): Promise { return new Promise((resolve, reject) => { let newContent = ""; @@ -58,7 +55,7 @@ function getChanges( warn: (...message: string[]) => options.log("conventional-changelog: ", ...message), }, { - version: bumpResult.next, + version: bumpResult.nextVersion, }, { merges: null, @@ -78,10 +75,16 @@ function getChanges( }); } +type UpdateChangelog = { + changelogPath: string; + oldContent: string; + newContent: string; +}; + export async function updateChangelog( options: ForkConfigOptions, - bumpResult: Awaited>, -) { + bumpResult: BumpVersion, +): Promise { if (options.header.search(RELEASE_PATTERN) !== -1) { throw new Error("Header cannot contain release pattern"); // Need to ensure the header doesn't contain the release pattern } diff --git a/src/commit.ts b/src/commit.ts index 8c20c85..cb90317 100644 --- a/src/commit.ts +++ b/src/commit.ts @@ -1,12 +1,18 @@ import { createExecute } from "./utils/execute-file.js"; import { formatCommitMessage } from "./utils/format-commit-message.js"; import type { ForkConfigOptions } from "./configuration.js"; -import type { bumpVersion } from "./version.js"; +import type { BumpVersion } from "./version.js"; + +type CommitChanges = { + filesToCommit: string[]; + gitAddOutput?: string; + gitCommitOutput?: string; +}; export async function commitChanges( options: ForkConfigOptions, - bumpResult: Awaited>, -) { + bumpResult: BumpVersion, +): Promise { const filesToCommit: string[] = [options.changelog]; for (const file of bumpResult.files) { @@ -35,8 +41,8 @@ export async function commitChanges( ...shouldCommitAll, "-m", formatCommitMessage( - options.changelogPresetConfig?.releaseCommitMessageFormat || "chore(release): {{currentTag}}", - bumpResult.next, + options.changelogPresetConfig?.releaseCommitMessageFormat, + bumpResult.nextVersion, ), ); diff --git a/src/tag.ts b/src/tag.ts index f66131b..905c1d1 100644 --- a/src/tag.ts +++ b/src/tag.ts @@ -1,16 +1,25 @@ import { createExecute } from "./utils/execute-file.js"; import { formatCommitMessage } from "./utils/format-commit-message.js"; import type { ForkConfigOptions } from "./configuration.js"; -import type { bumpVersion } from "./version.js"; +import type { BumpVersion } from "./version.js"; + +type TagChanges = { + gitTagOutput: string; + currentBranchName: string; + hasPackageJson: boolean; + pushMessage: string; + publishMessage: string; +}; export async function tagChanges( options: ForkConfigOptions, - bumpResult: Awaited>, -) { + bumpResult: BumpVersion, +): Promise { const { executeGit } = createExecute(options); const shouldSign = options.sign ? "-s" : "-a"; - const tag = `${options.tagPrefix}${bumpResult.next}`; + /** @example "v1.2.3" or "version/1.2.3" */ + const tag = `${options.tagPrefix}${bumpResult.nextVersion}`; const gitTagOutput = await executeGit( "tag", @@ -18,16 +27,28 @@ export async function tagChanges( tag, "-m", formatCommitMessage( - options.changelogPresetConfig?.releaseCommitMessageFormat || "chore(release): {{currentTag}}", - bumpResult.next, + options.changelogPresetConfig?.releaseCommitMessageFormat, + bumpResult.nextVersion, ), ); - // Get the current branch name - const gitRevParse = await executeGit("rev-parse", "--abbrev-ref", "HEAD"); + const currentBranchName = await executeGit("rev-parse", "--abbrev-ref", "HEAD"); + + const pushMessage = `Run \`git push --follow-tags origin ${currentBranchName.trim()}\` to push the tag.`; + + const hasPackageJson = bumpResult.files.some((file) => file.name === "package.json"); + const isPreRelease = `${bumpResult.releaseType}`.startsWith("pre"); + const publishMessage = isPreRelease + ? `Run \`npm publish --tag ${ + typeof options.preReleaseTag === "string" ? options.preReleaseTag : "prerelease" + }\` to publish the package.` + : `Run \`npm publish\` to publish the package.`; return { gitTagOutput, - gitRevParse, + currentBranchName, + hasPackageJson, + pushMessage, + publishMessage, }; } diff --git a/src/utils/execute-file.ts b/src/utils/execute-file.ts index bb106c5..28e7ac0 100644 --- a/src/utils/execute-file.ts +++ b/src/utils/execute-file.ts @@ -3,9 +3,9 @@ import type { ForkConfigOptions } from "../configuration.js"; export function createExecute(options: ForkConfigOptions) { /** - * Executes a git command with the given arguments. + * Executes a git command with the given arguments and returns the output. */ - async function executeGit(...execArgs: (string | undefined)[]) { + async function executeGit(...execArgs: (string | undefined)[]): Promise { const args = execArgs.filter(Boolean) as string[]; options.log(`Executing: git ${args.join(" ")}`); @@ -22,6 +22,8 @@ export function createExecute(options: ForkConfigOptions) { }); }); } + + return ""; } return { diff --git a/src/utils/format-commit-message.ts b/src/utils/format-commit-message.ts index 1be81e6..dfacf96 100644 --- a/src/utils/format-commit-message.ts +++ b/src/utils/format-commit-message.ts @@ -1,3 +1,13 @@ -export function formatCommitMessage(message: string, newVersion: string): string { +/** + * Formats the commit message by replacing the `{{currentTag}}` placeholder + * globally with the new version. + * + * Falls back to `chore(release): {{currentTag}}` if message is argument is falsy. + */ +export function formatCommitMessage(message: string | undefined, newVersion: string): string { + if (!message) { + message = "chore(release): {{currentTag}}"; + } + return message.replace(new RegExp("{{currentTag}}", "g"), newVersion); } diff --git a/src/version.ts b/src/version.ts index 0d876bc..40aeb9a 100644 --- a/src/version.ts +++ b/src/version.ts @@ -8,7 +8,15 @@ import detectNewLine from "detect-newline"; import { stringifyPackage } from "./libs/stringify-package.js"; import type { ForkConfigOptions } from "./configuration.js"; -function getFile(options: ForkConfigOptions, fileToGet: string) { +type FileState = { + name: string; + path: string; + type: "package-file" | ({} & string); // eslint-disable-line @typescript-eslint/ban-types + version: string; + isPrivate: boolean; +}; + +function getFile(options: ForkConfigOptions, fileToGet: string): FileState | undefined { try { const fileExtension = extname(fileToGet); if (fileExtension === ".json") { @@ -20,9 +28,13 @@ function getFile(options: ForkConfigOptions, fileToGet: string) { // Return if version property exists if (parsedJson.version) { return { + name: fileToGet, path: filePath, type: "package-file", version: parsedJson.version, + isPrivate: + "private" in parsedJson && + (typeof parsedJson.private === "boolean" ? parsedJson.private : false), }; } } @@ -32,7 +44,7 @@ function getFile(options: ForkConfigOptions, fileToGet: string) { } } -async function getLatestGitTagVersion(tagPrefix: string | undefined) { +async function getLatestGitTagVersion(tagPrefix: string | undefined): Promise { const gitTags = await gitSemverTags({ tagPrefix }); if (!gitTags.length) { return "1.0.0"; @@ -51,21 +63,22 @@ async function getLatestGitTagVersion(tagPrefix: string | undefined) { return cleanedTags.sort(semver.rcompare)[0]; } +type CurrentVersion = { + currentVersion: string; + files: FileState[]; +}; + /** * Get the current version from the given files and find their locations. */ -async function getCurrentVersion(options: ForkConfigOptions) { - const files: { path: string; name: string; type: string }[] = []; +async function getCurrentVersion(options: ForkConfigOptions): Promise { + const files: FileState[] = []; const versions: string[] = []; for (const file of options.outFiles) { const fileState = getFile(options, file); if (fileState) { - files.push({ - name: file, - path: fileState.path, - type: fileState.type, - }); + files.push(fileState); if (options.currentVersion) { continue; @@ -87,7 +100,7 @@ async function getCurrentVersion(options: ForkConfigOptions) { if (version) { return { files: [], - version, + currentVersion: version, }; } } @@ -99,7 +112,7 @@ async function getCurrentVersion(options: ForkConfigOptions) { return { files, - version: versions[0], + currentVersion: versions[0], }; } @@ -110,7 +123,7 @@ async function getCurrentVersion(options: ForkConfigOptions) { * - "minor" => 1 * - "major" => 2 */ -function getPriority(type?: string) { +function getPriority(type?: string): number { return ["patch", "minor", "major"].indexOf(type || ""); } @@ -121,7 +134,7 @@ function getPriority(type?: string) { * - "minor" * - "major" */ -function getVersionType(version: string) { +function getVersionType(version: string): "patch" | "minor" | "major" | undefined { const parseVersion = semver.parse(version); if (parseVersion?.major) { return "major"; @@ -160,22 +173,23 @@ function getReleaseType( return `pre${releaseType}`; } +type NextVersion = { + nextVersion: string; + level?: number; + preMajor?: boolean; + reason?: string; + releaseType?: ReleaseType; +}; + /** * Get the next version from the given files. */ async function getNextVersion( options: ForkConfigOptions, currentVersion: string, -): Promise<{ - version: string; - - level?: number; - preMajor?: boolean; - reason?: string; - releaseType?: ReleaseType; -}> { +): Promise { if (options.nextVersion && semver.valid(options.nextVersion)) { - return { version: options.nextVersion }; + return { nextVersion: options.nextVersion }; } const preMajor = semver.lt(currentVersion, "1.0.0"); @@ -199,7 +213,7 @@ async function getNextVersion( return Object.assign(recommendedBump, { preMajor, releaseType, - version: + nextVersion: semver.inc( currentVersion, releaseType, @@ -216,7 +230,7 @@ function updateFile( fileToUpdate: string, type: string, nextVersion: string, -) { +): void { try { if (type === "package-file") { if (!lstatSync(fileToUpdate).isFile()) return; @@ -240,32 +254,21 @@ function updateFile( } } -export async function bumpVersion(options: ForkConfigOptions): Promise<{ - current: string; - next: string; +export type BumpVersion = CurrentVersion & NextVersion; - files: { - name: string; - path: string; - type: string; - }[]; - level?: number; - preMajor?: boolean; - reason?: string; - releaseType?: ReleaseType; -}> { +export async function bumpVersion(options: ForkConfigOptions): Promise { const current = await getCurrentVersion(options); - const next = await getNextVersion(options, current.version); + const next = await getNextVersion(options, current.currentVersion); for (const outFile of current.files) { - updateFile(options, outFile.path, outFile.type, next.version); + updateFile(options, outFile.path, outFile.type, next.nextVersion); } return { - current: current.version, - next: next.version, - + currentVersion: current.currentVersion, files: current.files, + + nextVersion: next.nextVersion, level: next.level, preMajor: next.preMajor, reason: next.reason,