Skip to content

Commit

Permalink
package rename changelog validation (#157)
Browse files Browse the repository at this point in the history
* allows changelog to maintain the history when package has been renamed

* Update src/changelog.ts

removing redundant null check.

Co-authored-by: Jongsun Suh <[email protected]>

* Update src/changelog.ts

Co-authored-by: Jongsun Suh <[email protected]>

* validation test added for renamed package changelog

* Update src/changelog-config.ts

Co-authored-by: Jongsun Suh <[email protected]>

* Update src/changelog-config.ts

Co-authored-by: Jongsun Suh <[email protected]>

* command line params for version and tag prefix of the package before rename

* refactor unreleasedLinkReferenceDefinition

* refactor released link reference definition

* Apply JSDoc syntax for optional params

- see: https://jsdoc.app/tags-param.html#optional-parameters-and-default-values

* rename versionBeforePkgRename and tagPrefixBeforePkgRename and released links code refactor

* Revert "Apply JSDoc syntax for optional params
"

This reverts commit b51a4fa.

* added PackageRename type with version and tagPrefix properties

* parse changelog test

* Update src/changelog.ts

Co-authored-by: Elliot Winkler <[email protected]>

* Update src/changelog.ts

Co-authored-by: Elliot Winkler <[email protected]>

---------

Co-authored-by: Jongsun Suh <[email protected]>
Co-authored-by: Elliot Winkler <[email protected]>
  • Loading branch information
3 people authored Oct 17, 2023
1 parent 4804d3e commit a9594ab
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 37 deletions.
167 changes: 131 additions & 36 deletions src/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
unreleased,
Version,
} from './constants';
import { PackageRename } from './shared-types';

const changelogTitle = '# Changelog';
const changelogDescription = `All notable changes to this project will be documented in this file.
Expand Down Expand Up @@ -165,24 +166,50 @@ function getTagUrl(repoUrl: string, tag: string) {
* @param repoUrl - The URL for the GitHub repository.
* @param tagPrefix - The prefix used in tags before the version number.
* @param releases - The releases to generate link definitions for.
* @param packageRename - The package rename properties
* An optional, which is required only in case of package renamed.
* @returns The stringified release link definitions.
*/
function stringifyLinkReferenceDefinitions(
repoUrl: string,
tagPrefix: string,
releases: ReleaseMetadata[],
packageRename?: PackageRename,
) {
// A list of release versions in descending SemVer order
const descendingSemverVersions = releases
.map(({ version }) => version)
.sort((a: Version, b: Version) => {
return semver.gt(a, b) ? -1 : 1;
});
const latestSemverVersion = descendingSemverVersions[0];
// A list of release versions in chronological order
const chronologicalVersions = releases.map(({ version }) => version);
const hasReleases = chronologicalVersions.length > 0;
const unreleasedLinkReferenceDefinition =
getUnreleasedLinkReferenceDefinition(
repoUrl,
tagPrefix,
releases,
packageRename,
);

const releaseLinkReferenceDefinitions = getReleaseLinkReferenceDefinitions(
repoUrl,
tagPrefix,
releases,
packageRename,
).join('\n');
return `${unreleasedLinkReferenceDefinition}\n${releaseLinkReferenceDefinitions}${
releases.length > 0 ? '\n' : ''
}`;
}

/**
* Get a string of unreleased link reference definition.
*
* @param repoUrl - The URL for the GitHub repository.
* @param tagPrefix - The prefix used in tags before the version number.
* @param releases - The releases to generate link definitions for.
* @param packageRename - The package rename properties.
* @returns A unreleased link reference definition string.
*/
function getUnreleasedLinkReferenceDefinition(
repoUrl: string,
tagPrefix: string,
releases: ReleaseMetadata[],
packageRename?: PackageRename,
): string {
// The "Unreleased" section represents all changes made since the *highest*
// release, not the most recent release. This is to accomodate patch releases
// of older versions that don't represent the latest set of changes.
Expand All @@ -193,42 +220,102 @@ function stringifyLinkReferenceDefinitions(
//
// If there have not been any releases yet, the repo URL is used directly as
// the link definition.
const unreleasedLinkReferenceDefinition = `[${unreleased}]: ${

// A list of release versions in descending SemVer order
const descendingSemverVersions = releases
.map(({ version }) => version)
.sort((a: Version, b: Version) => {
return semver.gt(a, b) ? -1 : 1;
});
const latestSemverVersion = descendingSemverVersions[0];
const hasReleases = descendingSemverVersions.length > 0;
// if there is a package renamed, the tag prefix before the rename will be considered for compare
// [Unreleased]: https://github.com/ExampleUsernameOrOrganization/ExampleRepository/compare/[email protected]
const tagPrefixToCompare =
packageRename && packageRename.versionBeforeRename === latestSemverVersion
? packageRename.tagPrefixBeforeRename
: tagPrefix;

return `[${unreleased}]: ${
hasReleases
? getCompareUrl(repoUrl, `${tagPrefix}${latestSemverVersion}`, 'HEAD')
? getCompareUrl(
repoUrl,
`${tagPrefixToCompare}${latestSemverVersion}`,
'HEAD',
)
: withTrailingSlash(repoUrl)
}`;
}

/**
* Get a list of release link reference definitions.
*
* @param repoUrl - The URL for the GitHub repository.
* @param tagPrefix - The prefix used in tags before the version number.
* @param releases - The releases to generate link definitions for.
* @param packageRename - The package rename properties.
* @returns A list of release link reference definitions.
*/
function getReleaseLinkReferenceDefinitions(
repoUrl: string,
tagPrefix: string,
releases: ReleaseMetadata[],
packageRename?: PackageRename,
): string[] {
// The "previous" release that should be used for comparison is not always
// the most recent release chronologically. The _highest_ version that is
// lower than the current release is used as the previous release, so that
// patch releases on older releases can be accomodated.
const releaseLinkReferenceDefinitions = releases
.map(({ version }) => {
let diffUrl;
if (version === chronologicalVersions[chronologicalVersions.length - 1]) {
diffUrl = getTagUrl(repoUrl, `${tagPrefix}${version}`);
const chronologicalVersions = releases.map(({ version }) => version);
let tagPrefixToCompare = tagPrefix;
const releaseLinkReferenceDefinitions = releases.map(({ version }) => {
let diffUrl;
// once the version matches with versionBeforeRename, rest of the lines in changelog will be assumed as migrated tags
if (packageRename && packageRename.versionBeforeRename === version) {
tagPrefixToCompare = packageRename.tagPrefixBeforeRename;
}

if (version === chronologicalVersions[chronologicalVersions.length - 1]) {
diffUrl = getTagUrl(repoUrl, `${tagPrefixToCompare}${version}`);
} else {
const versionIndex = chronologicalVersions.indexOf(version);
const previousVersion = chronologicalVersions
.slice(versionIndex)
.find((releaseVersion: Version) => {
return semver.gt(version, releaseVersion);
});

if (previousVersion) {
if (
packageRename &&
packageRename.versionBeforeRename === previousVersion
) {
// The package was renamed at this version
// (the tag prefix holds the new name).
diffUrl = getCompareUrl(
repoUrl,
`${packageRename.tagPrefixBeforeRename}${previousVersion}`,
`${tagPrefix}${version}`,
);
} else {
// If the package was ever renamed, it was not renamed at this version,
// so use either the old tag prefix or the new tag prefix.
// If the package was never renamed, use the tag prefix as it is.
diffUrl = getCompareUrl(
repoUrl,
`${tagPrefixToCompare}${previousVersion}`,
`${tagPrefixToCompare}${version}`,
);
}
} else {
const versionIndex = chronologicalVersions.indexOf(version);
const previousVersion = chronologicalVersions
.slice(versionIndex)
.find((releaseVersion: Version) => {
return semver.gt(version, releaseVersion);
});
diffUrl = previousVersion
? getCompareUrl(
repoUrl,
`${tagPrefix}${previousVersion}`,
`${tagPrefix}${version}`,
)
: getTagUrl(repoUrl, `${tagPrefix}${version}`);
// This is the smallest release.
diffUrl = getTagUrl(repoUrl, `${tagPrefixToCompare}${version}`);
}
return `[${version}]: ${diffUrl}`;
})
.join('\n');
return `${unreleasedLinkReferenceDefinition}\n${releaseLinkReferenceDefinitions}${
releases.length > 0 ? '\n' : ''
}`;
}
return `[${version}]: ${diffUrl}`;
});

return releaseLinkReferenceDefinitions;
}

type AddReleaseOptions = {
Expand Down Expand Up @@ -265,28 +352,35 @@ export default class Changelog {

#formatter: Formatter;

readonly #packageRename: PackageRename | undefined;

/**
* Construct an empty changelog.
*
* @param options - Changelog options.
* @param options.repoUrl - The GitHub repository URL for the current project.
* @param options.tagPrefix - The prefix used in tags before the version number.
* @param options.formatter - A function that formats the changelog string.
* @param options.packageRename - The package rename properties.
* An optional, which is required only in case of package renamed.
*/
constructor({
repoUrl,
tagPrefix = 'v',
formatter = (changelog) => changelog,
packageRename,
}: {
repoUrl: string;
tagPrefix?: string;
formatter?: Formatter;
packageRename?: PackageRename;
}) {
this.#releases = [];
this.#changes = { [unreleased]: {} };
this.#repoUrl = repoUrl;
this.#tagPrefix = tagPrefix;
this.#formatter = formatter;
this.#packageRename = packageRename;
}

/**
Expand Down Expand Up @@ -468,6 +562,7 @@ ${stringifyLinkReferenceDefinitions(
this.#repoUrl,
this.#tagPrefix,
this.#releases,
this.#packageRename,
)}`;

return this.#formatter(changelog);
Expand Down
36 changes: 36 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { unreleased, Version } from './constants';
import { generateDiff } from './generate-diff';
import { createEmptyChangelog } from './init';
import { getRepositoryUrl } from './repo';
import { PackageRename } from './shared-types';
import { updateChangelog } from './update-changelog';
import {
ChangelogFormattingError,
Expand Down Expand Up @@ -142,6 +143,10 @@ type ValidateOptions = {
tagPrefix: string;
fix: boolean;
formatter: Formatter;
/**
* The package rename properties, used in case of package is renamed
*/
packageRename?: PackageRename;
};

/**
Expand All @@ -155,6 +160,8 @@ type ValidateOptions = {
* @param options.tagPrefix - The prefix used in tags before the version number.
* @param options.fix - Whether to attempt to fix the changelog or not.
* @param options.formatter - A custom Markdown formatter to use.
* @param options.packageRename - The package rename properties.
* An optional, which is required only in case of package renamed.
*/
async function validate({
changelogPath,
Expand All @@ -164,6 +171,7 @@ async function validate({
tagPrefix,
fix,
formatter,
packageRename,
}: ValidateOptions) {
const changelogContent = await readChangelog(changelogPath);

Expand All @@ -175,6 +183,7 @@ async function validate({
isReleaseCandidate,
tagPrefix,
formatter,
packageRename,
});
return undefined;
} catch (error) {
Expand Down Expand Up @@ -257,6 +266,14 @@ function configureCommonCommandOptions(_yargs: Argv) {
default: 'v',
description: 'The prefix used in tags before the version number.',
type: 'string',
})
.option('versionBeforePackageRename', {
description: 'A version of the package before being renamed.',
type: 'string',
})
.option('tagPrefixBeforePackageRename', {
description: 'A tag prefix of the package before being renamed.',
type: 'string',
});
}

Expand Down Expand Up @@ -332,6 +349,8 @@ async function main() {
tagPrefix,
fix,
prettier: usePrettier,
versionBeforePackageRename,
tagPrefixBeforePackageRename,
} = argv;
let { currentVersion } = argv;

Expand Down Expand Up @@ -408,6 +427,15 @@ async function main() {
return exitWithError(`Invalid repo URL: '${repoUrl}'`);
}

if (
(versionBeforePackageRename && !tagPrefixBeforePackageRename) ||
(!versionBeforePackageRename && tagPrefixBeforePackageRename)
) {
return exitWithError(
'--version-before-package-rename and --tag-prefix-before-package-rename must be given together or not at all.',
);
}

let changelogPath = changelogFilename;
if (!path.isAbsolute(changelogFilename) && projectRootDirectory) {
changelogPath = path.resolve(projectRootDirectory, changelogFilename);
Expand Down Expand Up @@ -447,6 +475,13 @@ async function main() {
formatter,
});
} else if (command === 'validate') {
let packageRename: PackageRename | undefined;
if (versionBeforePackageRename && tagPrefixBeforePackageRename) {
packageRename = {
versionBeforeRename: versionBeforePackageRename,
tagPrefixBeforeRename: tagPrefixBeforePackageRename,
};
}
await validate({
changelogPath,
currentVersion,
Expand All @@ -455,6 +490,7 @@ async function main() {
tagPrefix,
fix,
formatter,
packageRename,
});
} else if (command === 'init') {
await init({
Expand Down
Loading

0 comments on commit a9594ab

Please sign in to comment.