Skip to content

Commit dbdde9c

Browse files
authored
Add option to write only changelog md or json (#980)
1 parent cb2cca8 commit dbdde9c

7 files changed

Lines changed: 97 additions & 32 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"comment": "Add `'md'` and `'json'` options for `generateChangelog`",
3+
"type": "minor",
4+
"packageName": "beachball",
5+
"email": "elcraig@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

docs/overview/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ For the latest full list of supported options, see `RepoOptions` [in this file](
6767
| `defaultNpmTag` | string | `'latest'` | package | the default dist-tag used for NPM publish |
6868
| `disallowedChangeTypes` | string[] | | repo, group, package | what change types are disallowed |
6969
| `fetch` | bool | `true` | repo | fetch from remote before doing diff comparisons |
70-
| `generateChangelog` | bool | `true` | repo | whether to generate changelog files |
70+
| `generateChangelog` | bool, `'md'`, or `'json'` | `true` | repo | whether to generate `CHANGELOG.md/json` (`'md'` or `'json'` to generate only that type) |
7171
| `gitTags` | bool | `true` | repo, package | whether to create git tags for published packages (eg: foo_v1.0.1) |
7272
| `groups` | `VersionGroupOptions[]` ([details][3]) | | repo | specifies groups of packages that need to be version bumped at the same time |
7373
| `groupChanges` | bool | `false` | repo | will write multiple changes to a single changefile |

src/__e2e__/bump.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ describe('version bumping', () => {
602602
expect(modified).toContain('package2');
603603

604604
const changelogJson = readChangelogJson(repo.pathTo('packages/package2'));
605-
expect(changelogJson.entries[0].comments.patch![0].comment).toBe('Bump package1 to v0.0.2');
605+
expect(changelogJson?.entries[0].comments.patch![0].comment).toBe('Bump package1 to v0.0.2');
606606
});
607607

608608
it('calls sync prebump hook before packages are bumped', async () => {

src/__fixtures__/changelog.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,28 @@ import _ from 'lodash';
44
import { SortedChangeTypes } from '../changefile/changeTypes';
55
import { ChangelogJson } from '../types/ChangeLog';
66

7-
/** Read the CHANGELOG.md under the given package path, sanitizing any dates for snapshots */
8-
export function readChangelogMd(packagePath: string): string {
7+
/**
8+
* Read the CHANGELOG.md under the given package path, sanitizing any dates for snapshots.
9+
* Returns null if it doesn't exist.
10+
*/
11+
export function readChangelogMd(packagePath: string): string | null {
912
const changelogFile = path.join(packagePath, 'CHANGELOG.md');
13+
if (!fs.existsSync(changelogFile)) {
14+
return null;
15+
}
1016
const text = fs.readFileSync(changelogFile, { encoding: 'utf-8' });
1117
return text.replace(/\w\w\w, \d\d \w\w\w [\d :]+?GMT/gm, '(date)');
1218
}
1319

14-
/** Read the CHANGELOG.json under the given package path */
15-
export function readChangelogJson(packagePath: string, cleanForSnapshot: boolean = false): ChangelogJson {
20+
/**
21+
* Read the CHANGELOG.json under the given package path.
22+
* Returns null if it doesn't exist.
23+
*/
24+
export function readChangelogJson(packagePath: string, cleanForSnapshot: boolean = false): ChangelogJson | null {
1625
const changelogJsonFile = path.join(packagePath, 'CHANGELOG.json');
26+
if (!fs.existsSync(changelogJsonFile)) {
27+
return null;
28+
}
1729
const json = fs.readJSONSync(changelogJsonFile, { encoding: 'utf-8' });
1830
return cleanForSnapshot ? cleanChangelogJson(json) : json;
1931
}
@@ -22,7 +34,10 @@ export function readChangelogJson(packagePath: string, cleanForSnapshot: boolean
2234
* Clean changelog json for a snapshot: replace dates and SHAs with placeholders.
2335
* Note: this clones the changelog object rather than modifying the original.
2436
*/
25-
export function cleanChangelogJson(changelog: ChangelogJson): ChangelogJson {
37+
export function cleanChangelogJson(changelog: ChangelogJson | null): ChangelogJson | null {
38+
if (!changelog) {
39+
return null;
40+
}
2641
changelog = _.cloneDeep(changelog);
2742
// for a better snapshot, make the fake commit match if the real commit did
2843
const fakeCommits: { [commit: string]: string } = {};

src/__functional__/changelog/writeChangelog.test.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ import { writeChangelog } from '../../changelog/writeChangelog';
88
import { getPackageInfos } from '../../monorepo/getPackageInfos';
99
import { readChangeFiles } from '../../changefile/readChangeFiles';
1010
import { BeachballOptions } from '../../types/BeachballOptions';
11-
import { ChangeFileInfo } from '../../types/ChangeInfo';
11+
import { ChangeFileInfo, ChangeType } from '../../types/ChangeInfo';
1212
import type { Repository } from '../../__fixtures__/repository';
1313
import { getDefaultOptions } from '../../options/getDefaultOptions';
1414

15-
function getChange(packageName: string, comment: string): ChangeFileInfo {
15+
function getChange(packageName: string, comment: string, type: ChangeType = 'patch'): ChangeFileInfo {
1616
return {
1717
comment,
1818
email: 'test@testtestme.com',
1919
packageName,
20-
type: 'patch',
20+
type,
2121
dependentChangeType: 'patch',
2222
};
2323
}
@@ -77,7 +77,7 @@ describe('writeChangelog', () => {
7777
expect(cleanChangelogJson(changelogJson)).toMatchSnapshot('changelog json');
7878

7979
// Every entry should have a different commit hash
80-
const patchComments = changelogJson.entries[0].comments.patch!;
80+
const patchComments = changelogJson!.entries[0].comments.patch!;
8181
const commits = patchComments.map(entry => entry.commit);
8282
expect(new Set(commits).size).toEqual(patchComments.length);
8383

@@ -111,7 +111,7 @@ describe('writeChangelog', () => {
111111
expect(cleanChangelogJson(changelogJson)).toMatchSnapshot('changelog json');
112112

113113
// Every entry should have a different commit hash
114-
const patchComments = changelogJson.entries[0].comments.patch!;
114+
const patchComments = changelogJson!.entries[0].comments.patch!;
115115
const commits = patchComments.map(entry => entry.commit);
116116
expect(new Set(commits).size).toEqual(patchComments.length);
117117

@@ -151,7 +151,7 @@ describe('writeChangelog', () => {
151151
expect(readChangelogJson(repo.pathTo('packages/bar'), true /*clean*/)).toMatchSnapshot('bar CHANGELOG.json');
152152

153153
// Every entry should have a different commit hash
154-
const patchComments = fooJson.entries[0].comments.patch!;
154+
const patchComments = fooJson!.entries[0].comments.patch!;
155155
const commits = patchComments.map(entry => entry.commit);
156156
expect(new Set(commits).size).toEqual(patchComments.length);
157157

@@ -296,4 +296,44 @@ describe('writeChangelog', () => {
296296
// Validate grouped changelog for foo and bar packages
297297
expect(readChangelogMd(repo.pathTo('packages/foo'))).toMatchSnapshot();
298298
});
299+
300+
it('writes only CHANGELOG.md if generateChangelog is "md"', async () => {
301+
repo = repositoryFactory.cloneRepository();
302+
const options = getOptions({ generateChangelog: 'md' });
303+
304+
repo.commitChange('foo');
305+
generateChangeFiles([getChange('foo', 'comment 1')], options);
306+
307+
const packageInfos = getPackageInfos(repo.rootPath);
308+
const changes = readChangeFiles(options, packageInfos);
309+
310+
await writeChangelog(options, changes, { foo: 'patch' }, { foo: new Set(['foo']) }, packageInfos);
311+
312+
// CHANGELOG.md is written
313+
expect(readChangelogMd(repo.rootPath)).toContain('## 1.0.0');
314+
315+
// CHANGELOG.json is not written
316+
expect(readChangelogJson(repo.rootPath)).toBeNull();
317+
});
318+
319+
it('writes only CHANGELOG.json if generateChangelog is "json"', async () => {
320+
repo = repositoryFactory.cloneRepository();
321+
const options = getOptions({ generateChangelog: 'json' });
322+
323+
repo.commitChange('foo');
324+
generateChangeFiles([getChange('foo', 'comment 1')], options);
325+
326+
const packageInfos = getPackageInfos(repo.rootPath);
327+
const changes = readChangeFiles(options, packageInfos);
328+
329+
await writeChangelog(options, changes, { foo: 'patch' }, { foo: new Set(['foo']) }, packageInfos);
330+
331+
// CHANGELOG.md is not written
332+
expect(readChangelogMd(repo.rootPath)).toBeNull();
333+
334+
// CHANGELOG.json is written
335+
const changelogJson = readChangelogJson(repo.rootPath);
336+
expect(changelogJson).not.toBeNull();
337+
expect(changelogJson!.entries[0].comments.patch).toEqual([expect.objectContaining({ comment: 'comment 1' })]);
338+
});
299339
});

src/changelog/writeChangelog.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -123,25 +123,25 @@ async function writeChangelogFiles(
123123
let previousJson: ChangelogJson | undefined;
124124

125125
// Update CHANGELOG.json
126-
const changelogJsonFile = path.join(changelogPath, 'CHANGELOG.json');
127-
try {
128-
previousJson = fs.existsSync(changelogJsonFile) ? fs.readJSONSync(changelogJsonFile) : undefined;
129-
} catch (e) {
130-
console.warn(`${changelogJsonFile} is invalid: ${e}`);
131-
}
132-
try {
133-
const nextJson = renderJsonChangelog(newVersionChangelog, previousJson);
134-
fs.writeJSONSync(changelogJsonFile, nextJson, { spaces: 2 });
135-
} catch (e) {
136-
console.warn(`Problem writing to ${changelogJsonFile}: ${e}`);
126+
if (options.generateChangelog === true || options.generateChangelog === 'json') {
127+
const changelogJsonFile = path.join(changelogPath, 'CHANGELOG.json');
128+
try {
129+
previousJson = fs.existsSync(changelogJsonFile) ? fs.readJSONSync(changelogJsonFile) : undefined;
130+
} catch (e) {
131+
console.warn(`${changelogJsonFile} is invalid: ${e}`);
132+
}
133+
try {
134+
const nextJson = renderJsonChangelog(newVersionChangelog, previousJson);
135+
fs.writeJSONSync(changelogJsonFile, nextJson, { spaces: 2 });
136+
} catch (e) {
137+
console.warn(`Problem writing to ${changelogJsonFile}: ${e}`);
138+
}
137139
}
138140

139-
// Update CHANGELOG.md
141+
// Update CHANGELOG.md if there are changes of types besides "none"
140142
if (
141-
newVersionChangelog.comments.major ||
142-
newVersionChangelog.comments.minor ||
143-
newVersionChangelog.comments.patch ||
144-
newVersionChangelog.comments.prerelease
143+
(options.generateChangelog === true || options.generateChangelog === 'md') &&
144+
Object.entries(newVersionChangelog.comments).some(([type, comments]) => type !== 'none' && comments?.length)
145145
) {
146146
const changelogFile = path.join(changelogPath, 'CHANGELOG.md');
147147
const previousContent = fs.existsSync(changelogFile) ? fs.readFileSync(changelogFile).toString() : '';

src/types/BeachballOptions.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,13 @@ export interface RepoOptions {
109109
*/
110110
fetch: boolean;
111111
/**
112-
* Whether to generate changelog files
113-
* @default true
112+
* Whether to generate changelog files.
113+
* - `true` (default) to generate both CHANGELOG.md and CHANGELOG.json
114+
* - `false` to skip changelog generation
115+
* - `'md'` to generate only CHANGELOG.md
116+
* - `'json'` to generate only CHANGELOG.json
114117
*/
115-
generateChangelog: boolean;
118+
generateChangelog: boolean | 'md' | 'json';
116119
/** Options for bumping package versions together */
117120
groups?: VersionGroupOptions[];
118121
/**

0 commit comments

Comments
 (0)