Skip to content

Commit

Permalink
feat(version): option to add commit login username on each changelog …
Browse files Browse the repository at this point in the history
…entry, closes #248 (#272)

* feat(version): option to add commit login username on each changelog entry, closes #248
  • Loading branch information
ghiscoding committed Jul 27, 2022
1 parent 5c59e95 commit 2ca0dca
Show file tree
Hide file tree
Showing 18 changed files with 734 additions and 98 deletions.
4 changes: 2 additions & 2 deletions lerna.json
Expand Up @@ -11,8 +11,8 @@
"createRelease": "github",
"gitDryRun": false,
"syncWorkspaceLock": true,
"changelogIncludeCommitAuthorFullname": " (by _%a_)",
"changelogHeaderMessage": "### Automate your Workspace Versions, Changelogs & Publish with [Lerna-Lite](https://github.com/ghiscoding/lerna-lite) 🚀",
"changelogIncludeCommitsClientLogin": " - by @%l",
"changelogHeaderMessage": "### Automate your Workspace Versions, Changelogs & Publishing with [Lerna-Lite](https://github.com/ghiscoding/lerna-lite) 🚀",
"message": "chore(release): publish new version %v"
},
"run": {
Expand Down
19 changes: 16 additions & 3 deletions packages/cli/src/cli-commands/cli-version-commands.ts
Expand Up @@ -58,21 +58,28 @@ export default {
},
'changelog-header-message': {
describe:
'Add a custom message at the top of your "changelog.md" which is located in the root of your project. This option only works when using --conventional-commits.',
'Add a custom message at the top of your "changelog.md" which is located in the root of your project. This option is only available when using --conventional-commits with changelogs.',
group: 'Version Command Options:',
requiresArg: true,
type: 'string',
},
'changelog-include-commit-author-fullname': {
describe:
"Specify if we want to include the commit author's name, when using conventional-commits with changelog. We can optionally provide a custom message or else a default format will be used.",
"Specify if we want to include the commit author's name, this option is only available when using --conventional-commits with changelogs. We can also optionally provide a custom message or else a default format will be used.",
group: 'Version Command Options:',
requiresArg: false,
type: 'string',
},
'changelog-include-commits-client-login': {
describe:
'Specify if we want to include the commit remote client login name (ie GitHub username), this option is only available when using --conventional-commits with changelogs. We can also optionally provide a custom message or else a default format will be used.',
group: 'Version Command Options:',
requiresArg: false,
type: 'string',
},
'changelog-version-message': {
describe:
'Add a custom message as a prefix to each new version in your "changelog.md" which is located in the root of your project. This option only works when using --conventional-commits.',
'Add a custom message as a prefix to each new version in your "changelog.md" which is located in the root of your project. This option is only available when using --conventional-commits with changelogs.',
group: 'Version Command Options:',
requiresArg: true,
type: 'string',
Expand Down Expand Up @@ -192,6 +199,12 @@ export default {
requiresArg: true,
defaultDescription: 'alpha',
},
'remote-client': {
describe:
'Remote git client, which client is used when reading commits from remote which is useful when associating client login for each changelog entry.',
type: 'string',
choices: ['gitlab', 'github'],
},
'sign-git-commit': {
describe: 'Pass the `--gpg-sign` flag to `git commit`.',
type: 'boolean',
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/__mocks__/get-commits-since-last-release.ts
@@ -0,0 +1,2 @@
export const getCommitsSinceLastRelease = jest.fn(() => Promise.resolve());
export const getLastTagDetails = jest.fn(() => ({ tagHash: undefined, tagDate: undefined, tagRefCount: 0 }));
24 changes: 0 additions & 24 deletions packages/core/src/__mocks__/github-client.js

This file was deleted.

63 changes: 63 additions & 0 deletions packages/core/src/__mocks__/github-client.ts
@@ -0,0 +1,63 @@
const releases = new Map();

// keep test data isolated
afterEach(() => {
releases.clear();
});

export const graphqlCommitNodes = [
{
oid: 'deadbeef123456789',
author: {
name: 'Tester McPerson',
user: {
login: 'tester-mcperson',
},
},
message: 'fix(stuff): changed something',
},
{
oid: 'bee1234beef7890abc',
author: {
name: 'Tester McPerson',
user: {
login: 'tester-mcperson',
},
},
message: 'chore(thing): updated some small stuff',
},
];

const client = {
repos: {
createRelease: jest.fn((opts) => {
releases.set(opts.name, opts);
return Promise.resolve();
}),
},
graphql: jest.fn(() => {
return {
repository: {
ref: {
target: {
history: {
nodes: graphqlCommitNodes,
pageInfo: {
hasNextPage: false,
endCursor: '123abc456efg789',
},
},
},
},
},
};
}),
};

export const createGitHubClient: any = jest.fn(() => client);
createGitHubClient.releases = releases;

export const parseGitRepo = () => ({
owner: 'lerna',
name: 'lerna',
});
@@ -1,8 +1,6 @@
'use strict';

import fs from 'fs-extra';
import path from 'path';
const { Project } = require('../../project');
import { Project } from '../../project';

// helpers
import helpers, { gitAdd, gitCommit, gitTag } from '@lerna-test/helpers';
Expand All @@ -12,7 +10,6 @@ const initFixture = helpers.initFixtureFactory(__dirname);
import { recommendVersion, updateChangelog } from '../../conventional-commits';
import { Package } from '../../package';
import { GetChangelogConfig } from '../get-changelog-config';
// const { getChangelogConfig } = require('../lib/get-changelog-config');

// stabilize changelog commit SHA and datestamp
expect.addSnapshotSerializer(require('@lerna-test/helpers/serializers/serialize-changelog'));
Expand Down Expand Up @@ -418,7 +415,7 @@ describe('conventional-commits', () => {
`);
});

it('supports custom tagPrefix in fixed mode and include commit author full name', async () => {
it('supports custom tagPrefix in fixed mode when --changelog-include-commit-author-fullname is provided', async () => {
const cwd = await initFixture('fixed');

await gitTag(cwd, 'dragons-are-awesome1.0.0');
Expand Down Expand Up @@ -705,7 +702,7 @@ describe('conventional-commits', () => {
`);
});

it('updates independent changelogs and include commit author full name with a custom format when defined', async () => {
it('updates independent changelogs when providing --changelog-include-commit-author-fullname with a custom format when defined', async () => {
const cwd = await initFixture('independent');

await gitTag(cwd, 'package-1@1.0.0');
Expand All @@ -729,7 +726,7 @@ describe('conventional-commits', () => {

const opts = {
changelogPreset: 'conventional-changelog-angular',
changelogIncludeCommitAuthorFullname: ' by <**%a**>',
changelogIncludeCommitAuthorFullname: ' by (**%a**)',
};
const [changelogOne, changelogTwo] = await Promise.all([
updateChangelog(pkg1, 'independent', opts),
Expand All @@ -742,15 +739,85 @@ describe('conventional-commits', () => {
### Bug Fixes
* **stuff:** changed ([SHA](https://github.com/lerna/conventional-commits-independent/commit/GIT_HEAD)) by <**Tester McPerson**>
* **stuff:** changed ([SHA](https://github.com/lerna/conventional-commits-independent/commit/GIT_HEAD)) by (**Tester McPerson**)
`);
expect(changelogTwo.newEntry.trimRight()).toMatchInlineSnapshot(`
# [1.1.0](/compare/package-2@1.0.0...package-2@1.1.0) (YYYY-MM-DD)
### Features
* **thing:** added ([SHA](https://github.com/lerna/conventional-commits-independent/commit/GIT_HEAD)) by (**Tester McPerson**)
`);
});

it('updates independent changelogs when providing --changelog-include-commits-client-login with a custom format when defined', async () => {
const cwd = await initFixture('independent');

await gitTag(cwd, 'package-1@1.0.0');
await gitTag(cwd, 'package-2@1.0.0');

const [pkg1, pkg2] = await Project.getPackages(cwd);

// make a change in package-1 and package-2
await pkg1.set('changed', 1).serialize();
await pkg2.set('changed', 2).serialize();

await gitAdd(cwd, pkg1.manifestLocation);
const resultCommit1 = await gitCommit(cwd, 'fix(stuff): changed');

await gitAdd(cwd, pkg2.manifestLocation);
const resultCommit2 = await gitCommit(cwd, 'feat(thing): added');

// update versions
await pkg1.set('version', '1.0.1').serialize();
await pkg2.set('version', '1.1.0').serialize();

const opt1s = {
changelogPreset: 'conventional-changelog-angular',
changelogIncludeCommitsClientLogin: true,
commitsSinceLastRelease: [
{
authorName: 'Tester McPerson',
login: 'tester-mcperson',
shortHash: resultCommit1.stdout.match(/(\[main\s([0-9a-f]{7})\])/)[2],
message: 'fix(stuff): changed',
},
],
};
const opt2s = {
changelogPreset: 'conventional-changelog-angular',
changelogIncludeCommitsClientLogin: ' by (@%l, %a)',
commitsSinceLastRelease: [
{
authorName: 'Tester McPerson',
login: 'tester-mcperson',
shortHash: resultCommit2.stdout.match(/(\[main\s([0-9a-f]{7})\])/)[2],
message: 'feat(thing): added',
},
],
};

const [changelogOne, changelogTwo] = await Promise.all([
updateChangelog(pkg1, 'independent', opt1s),
updateChangelog(pkg2, 'independent', opt2s),
]);

expect(changelogOne.newEntry.trimRight()).toMatchInlineSnapshot(`
## [1.0.1](/compare/package-1@1.0.0...package-1@1.0.1) (YYYY-MM-DD)
### Bug Fixes
* **stuff:** changed ([SHA](https://github.com/lerna/conventional-commits-independent/commit/GIT_HEAD)) (@tester-mcperson)
`);
expect(changelogTwo.newEntry.trimRight()).toMatchInlineSnapshot(`
# [1.1.0](/compare/package-2@1.0.0...package-2@1.1.0) (YYYY-MM-DD)
### Features
* **thing:** added ([SHA](https://github.com/lerna/conventional-commits-independent/commit/GIT_HEAD)) by <**Tester McPerson**>
* **thing:** added ([SHA](https://github.com/lerna/conventional-commits-independent/commit/GIT_HEAD)) by (@tester-mcperson, Tester McPerson)
`);
});
});
Expand Down
@@ -0,0 +1,88 @@
jest.mock('../../utils/describe-ref');
jest.mock('../../child-process');
jest.mock('../get-github-commits');

import { getGithubCommits } from '../get-github-commits';
import { describeRefSync } from '../../utils/describe-ref';
import { getCommitsSinceLastRelease, getLastTagDetails } from '../get-commits-since-last-release';
import { execSync } from '../../child-process';

(execSync as jest.Mock).mockReturnValue('"deadbeef 2022-07-01T00:01:02-04:00"');

const tagStub = {
lastTagName: 'v1.0.0',
lastVersion: '1.0.0',
refCount: '1',
sha: 'deadbeef',
isDirty: false,
};
const commitsStub = [
{
authorName: 'Tester McPerson',
login: 'tester-mcperson',
shortHash: 'SHA',
message: 'fix(stuff): changed something',
},
];

describe('getCommitsSinceLastRelease', () => {
const execOpts = { cwd: '/test' };
beforeEach(() => {
(describeRefSync as jest.Mock).mockReturnValue(tagStub);
});

it('throws an error if used with a remote client other than "github"', async () => {
await expect(getCommitsSinceLastRelease('gitlab', 'durable', 'main', execOpts)).rejects.toThrow(
'Invalid remote client type, "github" is currently the only supported client with the option --changelog-include-commits-client-login.'
);
});

it('should expect commits returned when using "github" when a valid tag is returned', async () => {
(getGithubCommits as jest.Mock).mockResolvedValue(commitsStub);
const result = await getCommitsSinceLastRelease('github', 'durable', 'main', execOpts);

expect(result).toEqual(commitsStub);
});
});

describe('getLastTagDetails', () => {
const execOpts = { cwd: '/test' };

describe('with existing tag', () => {
beforeEach(() => {
(describeRefSync as jest.Mock).mockReturnValue(tagStub);
});

it('should expect a result with a tag date, hash and ref count', async () => {
const result = await getLastTagDetails(execOpts);
const execSpy = (execSync as jest.Mock).mockReturnValue('"deadbeef 2022-07-01T00:01:02-04:00"');

expect(execSpy).toHaveBeenCalledWith('git', ['log', '-1', '--format="%h %cI"', 'v1.0.0'], execOpts);
expect(result).toEqual({ tagDate: '2022-07-01T00:01:02-04:00', tagHash: 'deadbeef', tagRefCount: '1' });
});
});

describe('without tag', () => {
beforeEach(() => {
(describeRefSync as jest.Mock).mockReturnValue({
lastTagName: undefined,
lastVersion: undefined,
refCount: '1',
sha: 'deadbeef',
isDirty: false,
});
});

it('should return first commit date when describeRefSync() did not return a tag date', async () => {
const execSpy = (execSync as jest.Mock).mockReturnValue('"abcbeef 2022-07-01T00:01:02-04:00"');
const result = await getLastTagDetails(execOpts);

expect(execSpy).toHaveBeenCalledWith(
'git',
['log', '--oneline', '--format="%h %cI"', '--reverse', '--max-parents=0', 'HEAD'],
execOpts
);
expect(result).toEqual({ tagDate: '2022-07-01T00:01:02-04:00', tagHash: 'abcbeef', tagRefCount: '1' });
});
});
});

0 comments on commit 2ca0dca

Please sign in to comment.