Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ESLint configuration and packages #118

Merged
merged 5 commits into from Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions .eslintrc.js
Expand Up @@ -8,8 +8,6 @@ module.exports = {
files: ['*.ts'],
extends: ['@metamask/eslint-config-typescript'],
rules: {
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34960
'node/prefer-global/url': 'off',
},
Expand Down
23 changes: 12 additions & 11 deletions package.json
Expand Up @@ -40,23 +40,24 @@
},
"devDependencies": {
"@lavamoat/allow-scripts": "^1.0.6",
"@metamask/eslint-config": "^7.0.1",
"@metamask/eslint-config-jest": "^7.0.0",
"@metamask/eslint-config-nodejs": "^7.0.1",
"@metamask/eslint-config-typescript": "^7.0.1",
"@metamask/eslint-config": "^10.0.0",
"@metamask/eslint-config-jest": "^10.0.0",
"@metamask/eslint-config-nodejs": "^10.0.0",
"@metamask/eslint-config-typescript": "^10.0.0",
"@types/cross-spawn": "^6.0.2",
"@types/diff": "^5.0.0",
"@types/jest": "^26.0.23",
"@types/semver": "^7.3.6",
"@types/yargs": "^16.0.1",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",
"eslint": "^7.23.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.3.4",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^26.8.2",
"eslint-plugin-jsdoc": "^39.3.25",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^26.4.2",
"outdent": "^0.8.0",
"prettier": "^2.2.1",
Expand Down
84 changes: 70 additions & 14 deletions src/changelog.ts
Expand Up @@ -13,7 +13,7 @@ const changelogDescription = `All notable changes to this project will be docume
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).`;

interface ReleaseMetadata {
type ReleaseMetadata = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen this change made in a few commits and I'm curious about the motivation behind the shift from interface to type. Is this something we are slowly changing across the project, or is there a specific need that we are addressing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See here for more context: MetaMask/eslint-config#216 (and this Slack thread for even more context)

Primarily it was for consistency. No specific need being addressed. Erik referenced some difficulties encountered working with interfaces and our Json type, but I can't recall the details on that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, thanks for the context @Gudahtt!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Erik referenced some difficulties encountered working with interfaces and our Json type, but I can't recall the details on that.

It has to do with interfaces not having an index signature, IIRC.

/**
* The version of the current release.
*/
Expand All @@ -29,7 +29,7 @@ interface ReleaseMetadata {
* The status of the release (e.g. 'WITHDRAWN', 'DEPRECATED')
*/
status?: string;
}
};

/**
* Release changes, organized by category.
Expand All @@ -45,6 +45,13 @@ type ChangelogChanges = Record<Version, ReleaseChanges> & {

// Stringification helpers

/**
* Stringify a changelog category section.
*
* @param category - The title of the changelog category.
* @param changes - The changes included in this category.
* @returns The stringified category section.
*/
function stringifyCategory(category: ChangeCategory, changes: string[]) {
const categoryHeader = `### ${category}`;
if (changes.length === 0) {
Expand All @@ -56,6 +63,16 @@ function stringifyCategory(category: ChangeCategory, changes: string[]) {
return `${categoryHeader}\n${changeDescriptions}`;
}

/**
* Stringify a changelog release section.
*
* @param version - The release version.
* @param categories - The categories of changes included in this release.
* @param options - Additional release options.
* @param options.date - The date of the release.
* @param options.status - The status of the release (e.g., "DEPRECATED").
* @returns The stringified release section.
*/
function stringifyRelease(
version: Version | typeof unreleased,
categories: ReleaseChanges,
Expand All @@ -77,6 +94,13 @@ function stringifyRelease(
return `${releaseHeader}\n${categorizedChanges}`;
}

/**
* Stringify a set of changelog release sections.
*
* @param releases - The releases to stringify.
* @param changes - The set of changes to include, organized by release.
* @returns The stringified set of release sections.
*/
function stringifyReleases(
releases: ReleaseMetadata[],
changes: ChangelogChanges,
Expand All @@ -93,18 +117,49 @@ function stringifyReleases(
return [stringifiedUnreleased, ...stringifiedReleases].join('\n\n');
}

/**
* Return the given URL with a trailing slash. It is returned unaltered if it
* already has a trailing slash.
*
* @param url - The URL string.
* @returns The URL string with a trailing slash.
*/
function withTrailingSlash(url: string) {
return url.endsWith('/') ? url : `${url}/`;
}

/**
* Get the GitHub URL for comparing two git commits.
*
* @param repoUrl - The URL for the GitHub repository.
* @param firstRef - A reference (e.g., commit hash, tag, etc.) to the first commit to compare.
* @param secondRef - A reference (e.g., commit hash, tag, etc.) to the second commit to compare.
* @returns The comparison URL for the two given commits.
*/
function getCompareUrl(repoUrl: string, firstRef: string, secondRef: string) {
return `${withTrailingSlash(repoUrl)}compare/${firstRef}...${secondRef}`;
}

/**
* Get a GitHub tag URL.
*
* @param repoUrl - The URL for the GitHub repository.
* @param tag - The tag name.
* @returns The URL for the given tag.
*/
function getTagUrl(repoUrl: string, tag: string) {
return `${withTrailingSlash(repoUrl)}releases/tag/${tag}`;
}

/**
* Get a stringified list of link definitions for the given set of releases. The first release is
* linked to the corresponding tag, and each subsequent release is linked to a comparison with the
* previous release.
*
* @param repoUrl - The URL for the GitHub repository.
* @param releases - The releases to generate link definitions for.
* @returns The stringified release link definitions.
*/
function stringifyLinkReferenceDefinitions(
repoUrl: string,
releases: ReleaseMetadata[],
Expand Down Expand Up @@ -164,19 +219,19 @@ function stringifyLinkReferenceDefinitions(
}`;
}

interface AddReleaseOptions {
type AddReleaseOptions = {
addToStart?: boolean;
date?: string;
status?: string;
version: Version;
}
};

interface AddChangeOptions {
type AddChangeOptions = {
addToStart?: boolean;
category: ChangeCategory;
description: string;
version?: Version;
}
};

/**
* A changelog that complies with the
Expand All @@ -195,10 +250,10 @@ export default class Changelog {
private _repoUrl: string;

/**
* Construct an empty changelog
* Construct an empty changelog.
*
* @param options
* @param options.repoUrl - The GitHub repository URL for the current project
* @param options - Changelog options.
* @param options.repoUrl - The GitHub repository URL for the current project.
*/
constructor({ repoUrl }: { repoUrl: string }) {
this._releases = [];
Expand All @@ -209,15 +264,15 @@ export default class Changelog {
/**
* Add a release to the changelog.
*
* @param options
* @param options - Release options.
* @param options.addToStart - Determines whether the change is added to the
* top or bottom of the list of changes in this category. This defaults to
* `true` because changes should be in reverse-chronological order. This
* should be set to `false` when parsing a changelog top-to-bottom.
* @param options.date - An ISO-8601 formatted date, representing the release
* date.
* @param options.status - The status of the release (e.g. 'WITHDRAWN',
* 'DEPRECATED')
* @param options.status - The status of the release (e.g., 'WITHDRAWN',
* 'DEPRECATED').
* @param options.version - The version of the current release, which should
* be a [SemVer](https://semver.org/spec/v2.0.0.html)-compatible version.
*/
Expand All @@ -242,7 +297,7 @@ export default class Changelog {
/**
* Add a change to the changelog.
*
* @param options
* @param options - Change options.
* @param options.addToStart - Determines whether the change is added to the
* top or bottom of the list of changes in this category. This defaults to
* `true` because changes should be in reverse-chronological order. This
Expand Down Expand Up @@ -275,6 +330,7 @@ export default class Changelog {
if (!release[category]) {
release[category] = [];
}

if (addToStart) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
release[category]!.unshift(description);
Expand Down Expand Up @@ -361,7 +417,7 @@ export default class Changelog {
}

/**
* Gets all changes that have not yet been released
* Gets all changes that have not yet been released.
*
* @returns The changes that have not yet been released.
*/
Expand Down
76 changes: 67 additions & 9 deletions src/cli.ts
Expand Up @@ -40,6 +40,12 @@ formatting is correct. Verification of the contents is left for manual review.`;
// eslint-disable-next-line node/no-process-env
const npmPackageVersion = process.env.npm_package_version;

/**
* Determine whether the given URL is valid.
*
* @param proposedUrl - The URL to validate.
* @returns True if the URL is valid, false otherwise.
*/
function isValidUrl(proposedUrl: string) {
try {
// eslint-disable-next-line no-new
Expand All @@ -50,32 +56,59 @@ function isValidUrl(proposedUrl: string) {
}
}

/**
* Exit the process with the given error.
*
* @param errorMessage - The error message to exit with.
*/
function exitWithError(errorMessage: string) {
console.error(errorMessage);
process.exitCode = 1;
}

/**
* Read the changelog contents from the filesystem.
*
* @param changelogPath - The path to the changelog file.
* @returns The changelog contents.
*/
async function readChangelog(changelogPath: string) {
return await fs.readFile(changelogPath, {
encoding: 'utf8',
});
}

/**
* Save the changelog to the filesystem.
*
* @param changelogPath - The path to the changelog file.
* @param newChangelogContent - The new changelog contents to save.
*/
async function saveChangelog(
changelogPath: string,
newChangelogContent: string,
) {
await fs.writeFile(changelogPath, newChangelogContent);
}

interface UpdateOptions {
type UpdateOptions = {
changelogPath: string;
currentVersion?: Version;
repoUrl: string;
isReleaseCandidate: boolean;
projectRootDirectory?: string;
}

};

/**
* Update the changelog.
*
* @param options - Update options.
* @param options.changelogPath - The path to the changelog file.
* @param options.currentVersion - The current project version.
* @param options.isReleaseCandidate - Whether the current branch is a release candidate or not.
* @param options.repoUrl - The GitHub repository URL for the current project.
* @param options.projectRootDirectory - The root project directory.
*/
async function update({
changelogPath,
currentVersion,
Expand All @@ -101,13 +134,22 @@ async function update({
}
}

interface ValidateOptions {
type ValidateOptions = {
changelogPath: string;
currentVersion?: Version;
isReleaseCandidate: boolean;
repoUrl: string;
}

};

/**
* Validate the changelog.
*
* @param options - Validation options.
* @param options.changelogPath - The path to the changelog file.
* @param options.currentVersion - The current project version.
* @param options.isReleaseCandidate - Whether the current branch is a release candidate or not.
* @param options.repoUrl - The GitHub repository URL for the current project.
*/
async function validate({
changelogPath,
currentVersion,
Expand Down Expand Up @@ -137,11 +179,18 @@ async function validate({
}
}

interface InitOptions {
type InitOptions = {
changelogPath: string;
repoUrl: string;
}

};

/**
* Create a new empty changelog.
*
* @param options - Initialization options.
* @param options.changelogPath - The path to the changelog file.
* @param options.repoUrl - The GitHub repository URL for the current project.
*/
async function init({ changelogPath, repoUrl }: InitOptions) {
const changelogContent = await createEmptyChangelog({ repoUrl });
await saveChangelog(changelogPath, changelogContent);
Expand All @@ -152,6 +201,12 @@ look for changes since the last release (defaults to the entire repository at \
the current working directory), and where the changelog path is resolved from \
(defaults to the current working directory).`;

/**
* Configure options that are common to all commands.
*
* @param _yargs - The yargs instance to configure.
* @returns A Yargs instance configured with all common commands.
*/
function configureCommonCommandOptions(_yargs: Argv) {
return _yargs
.option('file', {
Expand All @@ -170,6 +225,9 @@ function configureCommonCommandOptions(_yargs: Argv) {
});
}

/**
* The entrypoint for the auto-changelog CLI.
*/
async function main() {
const { argv } = yargs(hideBin(process.argv))
.command(
Expand Down