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

feat(version): option to add commit login username on each changelog entry, closes #248 #272

Merged
merged 7 commits into from
Jul 27, 2022
4 changes: 2 additions & 2 deletions lerna.json
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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',
});
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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' });
});
});
});