diff --git a/src/changelog.ts b/src/changelog.ts index 34bcd99..695a1a6 100644 --- a/src/changelog.ts +++ b/src/changelog.ts @@ -140,20 +140,22 @@ export function makeChangeEntry(change: IChange, forProject: IProject): string { return line; } -function makeChangelogEntry(changes: IChange[], version: string, forProject: Project): string { - const formattedVersion = semver.parse(version).format(); // easy way of removing the leading 'v' +function makeChangelogEntry(changes: IChange[], version: string | null, forProject: Project): string { + const formattedVersion = version ? semver.parse(version).format() : null; // easy way of removing the leading 'v' const now = new Date(); - const lines = []; + const lines: string[] = []; - const padTwo = n => String(n).padStart(2, '0'); - lines.push(`Changes in ` + - `[${formattedVersion}]` + - `(https://github.com/${forProject.owner}/${forProject.repo}/releases/tag/v${formattedVersion}) ` + - `(${now.getFullYear()}-${padTwo(now.getMonth()+1)}-${padTwo(now.getDate())})`, - ); - lines.push('='.repeat(lines[0].length)); - lines.push(''); + if (version !== null) { + const padTwo = (n: number) => String(n).padStart(2, '0'); + lines.push(`Changes in ` + + `[${formattedVersion}]` + + `(https://github.com/${forProject.owner}/${forProject.repo}/releases/tag/v${formattedVersion}) ` + + `(${now.getFullYear()}-${padTwo(now.getMonth()+1)}-${padTwo(now.getDate())})`, + ); + lines.push('='.repeat(lines[0].length)); + lines.push(''); + } const shouldInclude = changes.filter(c => c.shouldInclude); const breaking = shouldInclude.filter(c => c.breaking); @@ -218,6 +220,10 @@ function isPrereleaseFor(version: SemVer, forVersion: SemVer): boolean { ); } +export async function previewChangelog(project: Project, changes: IChange[]) { + console.log(makeChangelogEntry(changes, null, project)); +} + export async function updateChangelog(project: Project, changes: IChange[], forVersion: string) { const forReleaseSemVer = semver.parse(forVersion); diff --git a/src/index.ts b/src/index.ts index 99a2bc0..7915400 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,7 @@ limitations under the License. */ import log from 'loglevel'; -import yargs from 'yargs/yargs'; +import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import clc from 'cli-color'; import semver from 'semver'; @@ -31,7 +31,7 @@ import { import { getLatestRelease, getReleaseBefore, getReleases, releasesContains } from "./releases"; import { ChangesByProject, getPackageJsonAtVersion, Project, branchExists, BranchMode } from './projects'; import { formatIssue } from './issue'; -import { updateChangelog } from './changelog'; +import { previewChangelog, updateChangelog } from './changelog'; import { Octokit } from '@octokit/rest'; function formatChangeType(changeType: ChangeType) { @@ -74,21 +74,35 @@ function printChangeStatus(change: IChange, projectName: string, owner: string, } async function main() { - const args = yargs(hideBin(process.argv)).option('debug', { - alias: 'd', - type: 'boolean', - description: "Enable debug mode", - }).option('check', { - type: 'boolean', - description: "Don't update changelog, just output information on what changes would be included", - }).help().usage("Usage: $0 [-d] [--check] ").argv; - - if (args._.length !== 1 && !args.check) { + const args = yargs(hideBin(process.argv)).version(false).options({ + "debug": { + alias: 'd', + type: 'boolean', + description: "Enable debug mode", + }, + "check": { + type: 'boolean', + description: "Don't update changelog, just output information on what changes would be included", + conflicts: ["preview"], + }, + "preview": { + type: "boolean", + description: "Generate changelog as normal, but without version header and output to STDOUT.", + conflicts: ["check"], + }, + }).command("* [version]", "Generate changelog for the given version", yargs => ( + yargs.positional("version", { + description: "The version to generate the changelog for, " + + "required if --check and/or --preview are not specified.", + type: "string", + }) + )).help().parseSync(); + + if (!args.version && !args.check && !args.preview) { // Surely yargs should be able to do this? It seems incredibly confusing and I already regret using it console.log("No version specified"); return; } - const targetRelease = args._[0] as string; if (args.debug) { log.setLevel(log.levels.DEBUG); @@ -109,26 +123,33 @@ async function main() { let fromVer: string; let toVer: string; - if (targetRelease) { - const targetReleaseSemVer = semver.parse(targetRelease); + if (args.version) { + const targetReleaseSemVer = semver.parse(args.version); const targetIsPrerelease = targetReleaseSemVer.prerelease.length > 0; const toVerReleaseBranch = `release-v${targetReleaseSemVer.major}.${targetReleaseSemVer.minor}.${targetReleaseSemVer.patch}`; - if (releasesContains(rels, targetRelease)) { - log.debug("Found existing release for " + targetRelease); + if (releasesContains(rels, args.version)) { + log.debug("Found existing release for " + args.version); // nb. getReleases only gets the most recent 100 so this won't work // for older releases - fromVer = getReleaseBefore(rels, targetRelease, targetIsPrerelease).name; - toVer = targetRelease; - } else if (targetRelease !== 'develop' && await branchExists(dir, toVerReleaseBranch)) { - log.debug("Found release branch for " + targetRelease); + fromVer = getReleaseBefore(rels, args.version, targetIsPrerelease).name; + toVer = args.version; + } else if (args.version !== 'develop' && await branchExists(dir, toVerReleaseBranch)) { + log.debug("Found release branch for " + args.version); // 'to' release has had a release branch cut but not yet a full release // compare to the tip of the release branch fromVer = getLatestRelease(rels, targetIsPrerelease).name; toVer = toVerReleaseBranch; branchMode = BranchMode.Release; + } else if (args.version !== 'develop' && await branchExists(dir, "staging")) { + log.debug("Found release branch for " + args.version); + // 'to' release has had a release branch cut but not yet a full release + // compare to the tip of the release branch + fromVer = getLatestRelease(rels, targetIsPrerelease).name; + toVer = "staging"; + branchMode = BranchMode.Release; } else { - log.debug("Found neither release nor branch for " + targetRelease); + log.debug("Found neither release nor branch for " + args.version); // the 'to' release is an doesn't-yet-exist future release - // compare to the tip of develop (a better piece of software // might make this configurable...) @@ -177,7 +198,7 @@ async function main() { const numBreaking = allChanges.filter(c => c.breaking).length; const numFeatures = allChanges.filter(c => c.changeType == ChangeType.FEATURE).length; - let suggestedBumpType; + let suggestedBumpType: "major" | "minor" | "patch"; if (numBreaking) { suggestedBumpType = 'major'; } else if (numFeatures) { @@ -193,8 +214,13 @@ async function main() { return; } - log.debug("Updating changelog entry for " + targetRelease); - await updateChangelog(project, allChanges, targetRelease); + if (args.preview) { + await previewChangelog(project, allChanges); + return; + } + + log.debug("Updating changelog entry for " + args.version); + await updateChangelog(project, allChanges, args.version); } main();