From 7106ec7d3e1a7c1009a11baf7b4b31a805d11d43 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Sat, 14 Jan 2023 00:50:47 -0500 Subject: [PATCH] feat(cli): changelog, package, expose output (#29) * cli, expose changelog, package options * cmds, files, index, pass changelog, packagePaths * global, allow prop functions --- README.md | 26 +++++----- bin/cli.js | 47 ++++++++++++++----- .../__snapshots__/global.test.js.snap | 1 + src/__tests__/cmds.test.js | 11 +++++ src/__tests__/files.test.js | 46 +++++++++--------- src/__tests__/global.test.js | 7 ++- src/__tests__/index.test.js | 16 +++++-- src/cmds.js | 18 +++---- src/files.js | 26 +++++----- src/global.js | 11 ++++- src/index.js | 5 +- tests/__snapshots__/code.test.js.snap | 4 +- 12 files changed, 138 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index fa75cfa..525a99f 100644 --- a/README.md +++ b/README.md @@ -36,27 +36,31 @@ or Yarn Usage: changelog [options] Options: - -b, --basic Keep updates to CHANGELOG.md basic, skip all markdown + -b, --basic Keep updates to [CHANGELOG.md] basic, skip all markdown link syntax [boolean] [default: false] - -c, --commit Commit CHANGELOG.md and package.json with a release + -c, --commit Commit [CHANGELOG.md] and package.json with a release commit [boolean] [default: true] - -d, --date CHANGELOG.md release date in the form of a valid date + -d, --date [CHANGELOG.md] release date in the form of a valid date string. Uses system new Date([your date]) - [string] [default: "2022-12-17T02:28:23.456Z"] + [string] [default: "2023-01-03T02:22:35.984Z"] -n, --non-cc Allow non-conventional commits to apply a semver weight - and appear in CHANGELOG.md under a general type + and appear in [CHANGELOG.md] under a general type description. [boolean] [default: false] -o, --override Use a version you define. [string] - -r, --dry-run Generate CHANGELOG.md sample output + -r, --dry-run Generate [CHANGELOG.md] sample output [boolean] [default: false] - --commit-path CHANGELOG.md path used for commits. This will be + --changelog Changelog output filename and relative path + [string] [default: "./CHANGELOG.md"] + --commit-path [CHANGELOG.md] path used for commits. This will be "joined" with "remote-url". Defaults to the commits path for GitHub. [string] [default: "commit/"] - --compare-path CHANGELOG.md path used for version comparison. This + --compare-path [CHANGELOG.md] path used for version comparison. This will be "joined" with "remote-url". Defaults to the comparison path for GitHub. [string] [default: "compare/"] - --pr-path CHANGELOG.md path used for PRs/MRs. This will be + --package package.json read, output and relative path + [string] [default: "./package.json"] + --pr-path [CHANGELOG.md] path used for PRs/MRs. This will be "joined" with "remote-url". Defaults to the PR path for GitHub. [string] [default: "pull/"] --release-message A list of prefix release scope commit messages. First @@ -64,8 +68,8 @@ or Yarn list item searches for the prior release message prefix for range. [write new, search old] [array] [default: "chore(release)"] - --remote-url Git remote get-url for updating CHANGELOG.md base urls. - This should start with "http". Defaults to "$ git + --remote-url Git remote get-url for updating [CHANGELOG.md] base + urls. This should start with "http". Defaults to "$ git remote get-url origin" [string] -h, --help Show help [boolean] -v, --version Show version number [boolean] diff --git a/bin/cli.js b/bin/cli.js index f055c24..27ac31a 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,5 +1,6 @@ #!/usr/bin/env node +const { join } = require('path'); const yargs = require('yargs'); const packageJson = require('../package'); const { commitChangelog, OPTIONS } = require('../src'); @@ -9,6 +10,7 @@ const { commitChangelog, OPTIONS } = require('../src'); */ const { basic: isBasic, + changelog: changelogFile, commit: isCommit, 'commit-path': commitPath, 'compare-path': comparePath, @@ -16,6 +18,7 @@ const { 'dry-run': isDryRun, 'non-cc': isAllowNonConventionalCommits, override: overrideVersion, + package: packageFile, 'pr-path': prPath, 'release-message': releaseTypeScope, 'remote-url': remoteUrl @@ -28,26 +31,26 @@ const { .option('b', { alias: 'basic', default: false, - describe: 'Keep updates to CHANGELOG.md basic, skip all markdown link syntax', + describe: 'Keep updates to [CHANGELOG.md] basic, skip all markdown link syntax', type: 'boolean' }) .option('c', { alias: 'commit', default: true, - describe: 'Commit CHANGELOG.md and package.json with a release commit', + describe: 'Commit [CHANGELOG.md] and package.json with a release commit', type: 'boolean' }) .option('d', { alias: 'date', default: new Date().toISOString(), - describe: 'CHANGELOG.md release date in the form of a valid date string. Uses system new Date([your date])', + describe: '[CHANGELOG.md] release date in the form of a valid date string. Uses system new Date([your date])', type: 'string' }) .option('n', { alias: 'non-cc', default: false, describe: - 'Allow non-conventional commits to apply a semver weight and appear in CHANGELOG.md under a general type description.', + 'Allow non-conventional commits to apply a semver weight and appear in [CHANGELOG.md] under a general type description.', type: 'boolean' }) .option('o', { @@ -58,25 +61,35 @@ const { .option('r', { alias: 'dry-run', default: false, - describe: 'Generate CHANGELOG.md sample output', + describe: 'Generate [CHANGELOG.md] sample output', type: 'boolean' }) + .option('changelog', { + default: './CHANGELOG.md', + describe: 'Changelog output filename and relative path', + type: 'string' + }) .option('commit-path', { default: 'commit/', describe: - 'CHANGELOG.md path used for commits. This will be "joined" with "remote-url". Defaults to the commits path for GitHub.', + '[CHANGELOG.md] path used for commits. This will be "joined" with "remote-url". Defaults to the commits path for GitHub.', type: 'string' }) .option('compare-path', { default: 'compare/', describe: - 'CHANGELOG.md path used for version comparison. This will be "joined" with "remote-url". Defaults to the comparison path for GitHub.', + '[CHANGELOG.md] path used for version comparison. This will be "joined" with "remote-url". Defaults to the comparison path for GitHub.', + type: 'string' + }) + .option('package', { + default: './package.json', + describe: 'package.json read, output and relative path', type: 'string' }) .option('pr-path', { default: 'pull/', describe: - 'CHANGELOG.md path used for PRs/MRs. This will be "joined" with "remote-url". Defaults to the PR path for GitHub.', + '[CHANGELOG.md] path used for PRs/MRs. This will be "joined" with "remote-url". Defaults to the PR path for GitHub.', type: 'string' }) .option('release-message', { @@ -87,20 +100,24 @@ const { }) .option('remote-url', { describe: - 'Git remote get-url for updating CHANGELOG.md base urls. This should start with "http". Defaults to "$ git remote get-url origin"', + 'Git remote get-url for updating [CHANGELOG.md] base urls. This should start with "http". Defaults to "$ git remote get-url origin"', type: 'string' }).argv; /** * Set global OPTIONS * - * @type {{comparePath: string, date: string, commitPath: string, isOverrideVersion: boolean, - * isAllowNonConventionalCommits: boolean, releaseTypeScope: string[], isDryRun: boolean, - * remoteUrl: string, prPath: string, isCommit: boolean, overrideVersion: string|*, - * isBasic: boolean}} + * @type {{comparePath: string, date: string, packagePath: Function, isOverrideVersion: boolean, + * packageFile: string, isAllowNonConventionalCommits: boolean, prPath: string, isCommit: boolean, + * overrideVersion: string|*, changelogPath: Function, commitPath: string, changelogFile: string, + * releaseTypeScope: string[]|string, isDryRun: boolean, remoteUrl: string, isBasic: boolean}} * @private */ OPTIONS._set = { + changelogFile, + changelogPath: function () { + return join(this.contextPath, changelogFile); + }, commitPath, comparePath, date, @@ -110,6 +127,10 @@ OPTIONS._set = { isCommit, isOverrideVersion: overrideVersion !== undefined, overrideVersion, + packageFile, + packagePath: function () { + return join(this.contextPath, packageFile); + }, prPath, releaseTypeScope, remoteUrl diff --git a/src/__tests__/__snapshots__/global.test.js.snap b/src/__tests__/__snapshots__/global.test.js.snap index 02c8505..f19de59 100644 --- a/src/__tests__/__snapshots__/global.test.js.snap +++ b/src/__tests__/__snapshots__/global.test.js.snap @@ -92,6 +92,7 @@ exports[`Global should set a one-time mutable OPTIONS object: immutable 1`] = ` "contextPath": "../src/__fixtures__", "dolor": "magna", "lorem": "ipsum", + "sit": "function test ../src/__fixtures__", }, "isFrozen": true, } diff --git a/src/__tests__/cmds.test.js b/src/__tests__/cmds.test.js index 6374057..f99ded7 100644 --- a/src/__tests__/cmds.test.js +++ b/src/__tests__/cmds.test.js @@ -1,7 +1,18 @@ +const { join } = require('path'); const cmds = require('../cmds'); const { OPTIONS } = require('../global'); describe('Commands', () => { + const { mockClear } = mockObjectProperty(OPTIONS, { + date: '2022-10-01', + changelogPath: join(OPTIONS.contextPath, 'CHANGELOG.md'), + packagePath: join(OPTIONS.contextPath, 'package.json') + }); + + afterAll(() => { + mockClear(); + }); + it('should attempt to run commands', () => { const { mockClear } = mockObjectProperty(OPTIONS, { releaseTypeScope: 'chore(release)' diff --git a/src/__tests__/files.test.js b/src/__tests__/files.test.js index 1ef4f80..651b3d4 100644 --- a/src/__tests__/files.test.js +++ b/src/__tests__/files.test.js @@ -1,8 +1,20 @@ // spell-checker: disable +const { join } = require('path'); const { updateChangelog, updatePackage } = require('../files'); const { getComparisonCommitHashes, parseCommits } = require('../parse'); +const { OPTIONS } = require('../global'); describe('Files', () => { + const { mockClear } = mockObjectProperty(OPTIONS, { + date: new Date('2022-10-01').toISOString(), + changelogPath: join(OPTIONS.contextPath, 'CHANGELOG.md'), + packagePath: join(OPTIONS.contextPath, 'package.json') + }); + + afterAll(() => { + mockClear(); + }); + it('should create, and update a basic CHANGELOG.md', () => { const commitLog = ` 1f12345b597123453031234555b6d25574ccacee refactor(file): lorem updates (#8) @@ -13,7 +25,7 @@ describe('Files', () => { `; const commitObj = parseCommits({ getGit: () => commitLog }); - expect(updateChangelog(commitObj, undefined, { date: '2022-10-01' })).toMatchSnapshot('changelog'); + expect(updateChangelog(commitObj, undefined)).toMatchSnapshot('changelog'); }); it('should create, and update CHANGELOG.md version with a comparison urls', () => { @@ -36,17 +48,10 @@ describe('Files', () => { }); expect( - updateChangelog( - commitObj, - '1.0.0', - { - date: '2022-10-01' - }, - { - getComparisonCommitHashes: () => comparisonObjNoReleaseCommit, - getRemoteUrls: () => urlObj - } - ) + updateChangelog(commitObj, '1.0.0', undefined, { + getComparisonCommitHashes: () => comparisonObjNoReleaseCommit, + getRemoteUrls: () => urlObj + }) ).toMatchSnapshot('urls and paths, no release commit'); const comparisonObjReleaseCommit = getComparisonCommitHashes({ @@ -55,17 +60,10 @@ describe('Files', () => { }); expect( - updateChangelog( - commitObj, - '1.0.0', - { - date: '2022-10-01' - }, - { - getComparisonCommitHashes: () => comparisonObjReleaseCommit, - getRemoteUrls: () => urlObj - } - ) + updateChangelog(commitObj, '1.0.0', undefined, { + getComparisonCommitHashes: () => comparisonObjReleaseCommit, + getRemoteUrls: () => urlObj + }) ).toMatchSnapshot('urls and paths, release commit'); expect( @@ -73,7 +71,7 @@ describe('Files', () => { commitObj, '1.0.0', { - date: '2022-10-01', + ...OPTIONS, isBasic: true }, { diff --git a/src/__tests__/global.test.js b/src/__tests__/global.test.js index 22c56f9..7f53cb7 100644 --- a/src/__tests__/global.test.js +++ b/src/__tests__/global.test.js @@ -9,7 +9,12 @@ describe('Global', () => { const { OPTIONS } = global; OPTIONS.lorem = 'et all'; OPTIONS.dolor = 'magna'; - OPTIONS._set = { lorem: 'ipsum' }; + OPTIONS._set = { + lorem: 'ipsum', + sit: function () { + return `function test ${this.contextPath}`; + } + }; OPTIONS.lorem = 'hello world'; OPTIONS.dolor = 'sit'; diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index 05ab5c9..279381d 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -1,18 +1,24 @@ +const { join } = require('path'); const { commitChangelog } = require('../index'); const { OPTIONS } = require('../global'); describe('Commit Changelog', () => { - it('should parse commits to produce a CHANGELOG.md', () => { - const { mockClear } = mockObjectProperty(OPTIONS, { date: new Date('2022-10-01').toISOString() }); - - expect(commitChangelog()).toMatchSnapshot('Commit Changelog'); + const { mockClear } = mockObjectProperty(OPTIONS, { + date: new Date('2022-10-01').toISOString(), + changelogPath: join(OPTIONS.contextPath, 'CHANGELOG.md'), + packagePath: join(OPTIONS.contextPath, 'package.json') + }); + afterAll(() => { mockClear(); }); + it('should parse commits to produce a CHANGELOG.md', () => { + expect(commitChangelog()).toMatchSnapshot('Commit Changelog'); + }); + it('should parse commits to produce a CHANGELOG.md with an override version', () => { const { mockClear } = mockObjectProperty(OPTIONS, { - date: new Date('2022-10-01').toISOString(), isOverrideVersion: true, overrideVersion: '15.0.0' }); diff --git a/src/cmds.js b/src/cmds.js index cee0273..a8f3a18 100644 --- a/src/cmds.js +++ b/src/cmds.js @@ -28,18 +28,18 @@ const runCmd = (cmd, { errorMessage = 'Skipping... {0}' } = {}) => { * * @param {*|string} version * @param {object} options - * @param {string} options.contextPath + * @param {string} options.changelogPath + * @param {string} options.packagePath * @param {string[]|string} options.releaseTypeScope * @returns {string} */ -const commitFiles = (version, { contextPath, releaseTypeScope } = OPTIONS) => { +const commitFiles = (version, { changelogPath, packagePath, releaseTypeScope } = OPTIONS) => { const isArray = Array.isArray(releaseTypeScope); return runCmd( - `git add ${join(contextPath, 'package.json')} ${join(contextPath, 'CHANGELOG.md')} && git commit ${join( - contextPath, - 'package.json' - )} ${join(contextPath, 'CHANGELOG.md')} -m "${(isArray && releaseTypeScope?.[0]) || releaseTypeScope}: ${version}"`, + `git add ${packagePath} ${changelogPath} && git commit ${packagePath} ${changelogPath} -m "${ + (isArray && releaseTypeScope?.[0]) || releaseTypeScope + }: ${version}"`, 'Skipping release commit... {0}' ); }; @@ -48,11 +48,11 @@ const commitFiles = (version, { contextPath, releaseTypeScope } = OPTIONS) => { * Get current package.json version * * @param {object} options - * @param {string} options.contextPath + * @param {string} options.packagePath * @returns {*} */ -const getCurrentVersion = ({ contextPath } = OPTIONS) => { - const { version } = require(join(contextPath, 'package.json')); +const getCurrentVersion = ({ packagePath } = OPTIONS) => { + const { version } = require(packagePath); return version; }; diff --git a/src/files.js b/src/files.js index ff18cb8..7fe4aed 100644 --- a/src/files.js +++ b/src/files.js @@ -1,5 +1,5 @@ +const { dirname } = require('path'); const { existsSync, readFileSync, writeFileSync } = require('fs'); -const { join } = require('path'); const { OPTIONS } = require('./global'); const { getRemoteUrls, runCmd } = require('./cmds'); const { getComparisonCommitHashes } = require('./parse'); @@ -14,12 +14,12 @@ const { getComparisonCommitHashes } = require('./parse'); * @param {object} parsedCommits * @param {*|string} packageVersion * @param {object} options + * @param {string} options.changelogPath * @param {string} options.date * @param {boolean} options.isBasic * @param {boolean} options.isDryRun * @param {object} settings * @param {string} settings.fallbackPackageVersion - * @param {string} settings.filePath * @param {string} settings.headerMd * @returns {string} @@ -27,10 +27,9 @@ const { getComparisonCommitHashes } = require('./parse'); const updateChangelog = ( parsedCommits = {}, packageVersion, - { date, isBasic = false, isDryRun = false } = OPTIONS, + { date, changelogPath, isBasic = false, isDryRun = false } = OPTIONS, { fallbackPackageVersion = '¯\\_(ツ)_/¯', - filePath = join(OPTIONS.contextPath, `/CHANGELOG.md`), getComparisonCommitHashes: getAliasComparisonCommitHashes = getComparisonCommitHashes, getRemoteUrls: getAliasRemoteUrls = getRemoteUrls, headerMd = `# Changelog\nAll notable changes to this project will be documented in this file.` @@ -44,12 +43,12 @@ const updateChangelog = ( let version = fallbackPackageVersion; let body = ''; - if (existsSync(filePath)) { - const [tempHeader, ...tempBody] = readFileSync(filePath, 'utf-8').split('##'); + if (existsSync(changelogPath)) { + const [tempHeader, ...tempBody] = readFileSync(changelogPath, 'utf-8').split('##'); header = tempHeader; body = (tempBody.length && `## ${tempBody.join('##')}`) || body; - } else { - writeFileSync(filePath, ''); + } else if (!isDryRun) { + writeFileSync(changelogPath, ''); } const displayCommits = Object.values(parsedCommits) @@ -73,7 +72,7 @@ const updateChangelog = ( if (isDryRun) { console.info(`\n${updatedBody}`); } else { - writeFileSync(filePath, output); + writeFileSync(changelogPath, output); } return output; @@ -85,13 +84,18 @@ const updateChangelog = ( * @param {'major'|'minor'|'patch'|*} versionBump * @param {object} options * @param {boolean} options.isDryRun + * @param {string} options.packagePath * @returns {string} */ -const updatePackage = (versionBump, { isDryRun = false } = OPTIONS) => { +const updatePackage = (versionBump, { isDryRun = false, packagePath } = OPTIONS) => { const output = `Version bump: ${versionBump}`; + const directory = dirname(packagePath); if (!isDryRun) { - runCmd(`npm version ${versionBump} --git-tag-version=false`, 'Skipping package.json version... {0}'); + runCmd( + `(cd ${directory} && npm version ${versionBump} --git-tag-version=false)`, + 'Skipping package.json version... {0}' + ); } return output; diff --git a/src/global.js b/src/global.js index 7e9ee17..5992031 100644 --- a/src/global.js +++ b/src/global.js @@ -70,12 +70,19 @@ const conventionalCommitType = (types => { /** * Global options/settings. One time _set, then freeze. * - * @type {{_set: *}} + * @type {{contextPath: string, _set: *}} */ const OPTIONS = { contextPath, set _set(obj) { - Object.entries(obj).forEach(([key, value]) => (this[key] = value)); + Object.entries(obj).forEach(([key, value]) => { + if (typeof value === 'function') { + this[key] = value.call(this); + return; + } + + this[key] = value; + }); delete this._set; Object.freeze(this); } diff --git a/src/index.js b/src/index.js index d38affd..3013998 100644 --- a/src/index.js +++ b/src/index.js @@ -7,6 +7,7 @@ const { updateChangelog, updatePackage } = require('./files'); * Set changelog and package. * * @param {object} options + * @param {string} options.changelogFile * @param {string} options.contextPath * @param {boolean} options.isCommit * @param {boolean} options.isDryRun @@ -24,7 +25,7 @@ const { updateChangelog, updatePackage } = require('./files'); * package: string, versionClean: *, changelog: string, semverWeight: number, version: *}} */ const commitChangelog = ( - { contextPath, isCommit, isDryRun, overrideVersion } = OPTIONS, + { changelogFile, contextPath, isCommit, isDryRun, overrideVersion } = OPTIONS, { commitFiles: commitAliasFiles = commitFiles, getOverrideVersion: getAliasOverrideVersion = getOverrideVersion, @@ -46,7 +47,7 @@ const commitChangelog = ( if (isDryRun) { console.info( color.CYAN, - `\nDry run CHANGELOG.md output...\nVersion: ${version}\nSemver bump: ${bump}\nSemver weight: ${weight}` + `\nDry run ${changelogFile} output...\nVersion: ${version}\nSemver bump: ${bump}\nSemver weight: ${weight}` ); } diff --git a/tests/__snapshots__/code.test.js.snap b/tests/__snapshots__/code.test.js.snap index 9a65831..345e0fc 100644 --- a/tests/__snapshots__/code.test.js.snap +++ b/tests/__snapshots__/code.test.js.snap @@ -5,8 +5,8 @@ exports[`General code checks should only have specific console.[warn|log|info|er "cmds.js:20: console.error(color.RED, errorMessage.replace('{0}', e.message), color.NOCOLOR)", "cmds.js:149: console.error(color.RED, \`Semver: \${e.message}\`, color.NOCOLOR)", "cmds.js:179: console.error(color.RED, \`Semver: \${e.message}\`, color.NOCOLOR)", - "files.js:74: console.info(\` \${updatedBody}\`)", + "files.js:73: console.info(\` \${updatedBody}\`)", "global.js:64: console.error(color.RED, \`Conventional commit types: \${e.message}\`, color.NOCOLOR)", - "index.js:47: console.info( index.js:61: console.info(color.NOCOLOR)", + "index.js:48: console.info( index.js:62: console.info(color.NOCOLOR)", ] `;