Skip to content

Commit 305b54d

Browse files
feat: option to specify additional json files to update version number
1 parent c1a7f9e commit 305b54d

File tree

8 files changed

+316
-3
lines changed

8 files changed

+316
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ Important: merge commits messages are ignored by the tool when calculating next
9797
| **`--skipCommitTypes`** | `string[]` | `[]` | treat commits with specified types as non invoking version bump ([details](https://github.com/jscutlery/semver#skipping-release-for-specific-types-of-commits)) |
9898
| **`--skipCommit`** | `boolean` | `false` | skips generating a new commit, leaves all changes in index, tag would be put on last commit ([details](https://github.com/jscutlery/semver#skipping-commit)) |
9999
| **`--commitMessageFormat`** | `string` | `undefined` | format the auto-generated message commit ([details](https://github.com/jscutlery/semver#commit-message-customization)) |
100+
| **`--customJsonPaths`** | `string[]` | `undefined` | another json files to update version. Values should be like: 'src/version.json:build.version'. Part after colon says path to attribute |
100101
| **`--preset`** | `string \| object` | `'angular'` | customize Conventional Changelog options ([details](https://github.com/jscutlery/semver#customizing-conventional-changelog-options)) |
101102

102103
#### Overwrite default configuration

packages/semver/src/executors/version/index.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ describe('@jscutlery/semver:version', () => {
3030
project.updatePackageJson as jest.MockedFunction<
3131
typeof project.updatePackageJson
3232
>;
33+
const mockUpdateCustomJsons =
34+
project.updateCustomJsons as jest.MockedFunction<
35+
typeof project.updateCustomJsons
36+
>;
3337
const mockUpdateChangelog = changelog.updateChangelog as jest.MockedFunction<
3438
typeof changelog.updateChangelog
3539
>;
@@ -107,6 +111,17 @@ describe('@jscutlery/semver:version', () => {
107111
mockUpdatePackageJson.mockImplementation(({ projectRoot }) =>
108112
of(project.getPackageJsonPath(projectRoot))
109113
);
114+
mockUpdateCustomJsons.mockImplementation(
115+
({ projectRoot, customJsonPaths }) => {
116+
const result: string[] = [];
117+
if (customJsonPaths) {
118+
for (const v of customJsonPaths) {
119+
result.push('file:' + v.split(':')[0]);
120+
}
121+
}
122+
return of(result);
123+
}
124+
);
110125
mockCalculateChangelogChanges.mockReturnValue((source) => {
111126
source.subscribe();
112127
return of('');
@@ -150,6 +165,10 @@ describe('@jscutlery/semver:version', () => {
150165
expect(mockUpdateChangelog).toHaveBeenCalledBefore(
151166
mockUpdatePackageJson as jest.Mock
152167
);
168+
expect(mockUpdatePackageJson).toHaveBeenCalledBefore(
169+
mockUpdateCustomJsons as jest.Mock
170+
);
171+
153172
expect(mockCommit).toHaveBeenCalledBefore(mockCreateTag as jest.Mock);
154173
expect(mockCreateTag).toHaveBeenCalledBefore(mockTryPush as jest.Mock);
155174
expect(mockTryPush).toHaveBeenCalledBefore(mockRunPostTargets as jest.Mock);
@@ -784,4 +803,25 @@ describe('@jscutlery/semver:version', () => {
784803
);
785804
});
786805
});
806+
807+
describe('--customJsonPaths', () => {
808+
it('should use --customJsonPaths ', async () => {
809+
const { success } = await version(
810+
{ ...options, customJsonPaths: ['src/version.json:version'] },
811+
context
812+
);
813+
814+
expect(success).toBe(true);
815+
816+
expect(mockUpdateCustomJsons).toBeCalledWith(
817+
expect.objectContaining({
818+
customJsonPaths: ['src/version.json:version'],
819+
})
820+
);
821+
822+
expect(mockAddToStage).toHaveBeenLastCalledWith(
823+
expect.objectContaining({ paths: ['file:src/version.json'] })
824+
);
825+
});
826+
});
787827
});

packages/semver/src/executors/version/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export default async function version(
4848
allowEmptyRelease,
4949
skipCommitTypes,
5050
skipCommit,
51+
customJsonPaths,
5152
} = _normalizeOptions(options);
5253
const workspaceRoot = context.root;
5354
const projectName = context.projectName as string;
@@ -128,6 +129,7 @@ export default async function version(
128129
changelogHeader,
129130
workspaceRoot,
130131
projectName,
132+
customJsonPaths,
131133
skipProjectChangelog,
132134
commitMessage,
133135
dependencyUpdates,
@@ -232,6 +234,7 @@ function _normalizeOptions(options: VersionBuilderSchema) {
232234
versionTagPrefix: options.tagPrefix ?? options.versionTagPrefix,
233235
commitMessageFormat: options.commitMessageFormat as string,
234236
skipCommit: options.skipCommit as boolean,
237+
customJsonPaths: options.customJsonPaths as string[],
235238
preset:
236239
options.preset === 'conventional'
237240
? 'conventionalcommits'

packages/semver/src/executors/version/schema.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface VersionBuilderSchema {
4343
allowEmptyRelease?: boolean;
4444
skipCommitTypes?: string[];
4545
commitMessageFormat?: string;
46+
customJsonPaths?: string[];
4647
preset: Preset;
4748
}
4849

packages/semver/src/executors/version/utils/logger.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type Step =
88
| 'warning'
99
| 'calculate_version_success'
1010
| 'package_json_success'
11+
| 'custom_json_success'
1112
| 'changelog_success'
1213
| 'tag_success'
1314
| 'post_target_success'
@@ -22,6 +23,7 @@ const iconMap = new Map<Step, string>([
2223
['changelog_success', '📜'],
2324
['commit_success', '📦'],
2425
['package_json_success', '📝'],
26+
['custom_json_success', '📝'],
2527
['post_target_success', '🎉'],
2628
['tag_success', '🔖'],
2729
['push_success', '🚀'],

packages/semver/src/executors/version/utils/project.spec.ts

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import * as fs from 'fs';
22
import { lastValueFrom } from 'rxjs';
33

4-
import { readPackageJson } from './project';
4+
import {
5+
readPackageJson,
6+
updateCustomJson,
7+
updateCustomJsons,
8+
} from './project';
9+
import { PathLike } from 'fs';
10+
import { FileHandle } from 'fs/promises';
11+
import { Stream } from 'stream';
512

613
const fsPromises = fs.promises;
714

@@ -15,3 +22,136 @@ describe('readPackageJson', () => {
1522
});
1623
});
1724
});
25+
26+
describe('Update custom version into json', () => {
27+
afterEach(() => {
28+
jest.clearAllMocks();
29+
});
30+
31+
it('should update version in JSON content - variant 1', async () => {
32+
jest.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
33+
jest
34+
.spyOn(fsPromises, 'readFile')
35+
.mockResolvedValue(`{"info":{"version":"2.1.0"}}`);
36+
jest
37+
.spyOn(fsPromises, 'writeFile')
38+
.mockImplementation(
39+
async (
40+
file: PathLike | FileHandle,
41+
data:
42+
| string
43+
| NodeJS.ArrayBufferView
44+
| Iterable<string | NodeJS.ArrayBufferView>
45+
| AsyncIterable<string | NodeJS.ArrayBufferView>
46+
| Stream
47+
) => {
48+
expect(data).toBe(`{"info":{"version":"1.2.3"}}\n`);
49+
return;
50+
}
51+
);
52+
const s = updateCustomJson({
53+
newVersion: '1.2.3',
54+
projectName: 'test',
55+
dryRun: false,
56+
projectRoot: 'test',
57+
customJsonPath: 'src/version.json:info.version',
58+
});
59+
await lastValueFrom(s);
60+
});
61+
62+
it('should return null on dryRun', async () => {
63+
const s = updateCustomJson({
64+
newVersion: '1.2.3',
65+
projectName: 'test',
66+
dryRun: true,
67+
projectRoot: 'test',
68+
customJsonPath: 'src/version.json:info.version',
69+
});
70+
71+
const resp = await lastValueFrom(s);
72+
expect(resp).toBe(null);
73+
});
74+
75+
it('should return empty array on undefined customJsonPaths', async () => {
76+
const s = updateCustomJsons({
77+
newVersion: '1.2.3',
78+
projectName: 'test',
79+
dryRun: false,
80+
projectRoot: 'test',
81+
});
82+
83+
const resp = await lastValueFrom(s);
84+
expect(resp).toBeArrayOfSize(0);
85+
});
86+
87+
it('should update version in multiple JSON contents', async () => {
88+
const result: string[] = [];
89+
jest.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
90+
jest
91+
.spyOn(fsPromises, 'readFile')
92+
.mockImplementation(async (path: PathLike | FileHandle) => {
93+
if (path.toString().includes('file1.json')) {
94+
return '{"version":"0.0.0"}';
95+
}
96+
if (path.toString().includes('file2.json')) {
97+
return '{"info":{"version":"0.0.0"}}';
98+
}
99+
return '';
100+
});
101+
jest
102+
.spyOn(fsPromises, 'writeFile')
103+
.mockImplementation(
104+
async (
105+
file: PathLike | FileHandle,
106+
data:
107+
| string
108+
| NodeJS.ArrayBufferView
109+
| Iterable<string | NodeJS.ArrayBufferView>
110+
| AsyncIterable<string | NodeJS.ArrayBufferView>
111+
| Stream
112+
) => {
113+
if (file.toString().includes('file1.json')) {
114+
result.push(data as string);
115+
}
116+
if (file.toString().includes('file2.json')) {
117+
result.push(data as string);
118+
}
119+
}
120+
);
121+
122+
const s = updateCustomJsons({
123+
newVersion: '1.2.3',
124+
projectName: 'test',
125+
dryRun: false,
126+
projectRoot: 'test',
127+
customJsonPaths: [
128+
'src/file1.json:version',
129+
'src/file2.json:info.version',
130+
],
131+
});
132+
await lastValueFrom(s);
133+
134+
expect(result).toContainAllValues([
135+
'{"version":"1.2.3"}\n',
136+
'{"info":{"version":"1.2.3"}}\n',
137+
]);
138+
});
139+
140+
it('should not touch file and should return empty array on dryRun', async () => {
141+
const mock = jest.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
142+
const s = updateCustomJsons({
143+
newVersion: '1.2.3',
144+
projectName: 'test',
145+
dryRun: true,
146+
projectRoot: 'test',
147+
customJsonPaths: [
148+
'src/file1.json:version',
149+
'src/file2.json:info.version',
150+
],
151+
});
152+
153+
const resp = await lastValueFrom(s);
154+
expect(mock).not.toBeCalled();
155+
expect(resp).toBeArrayOfSize(0);
156+
});
157+
});

0 commit comments

Comments
 (0)