Skip to content

Commit

Permalink
feat: option to specify additional json files to update version number
Browse files Browse the repository at this point in the history
  • Loading branch information
mvrana-cen81948 committed Feb 1, 2023
1 parent c1a7f9e commit 37f6454
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Important: merge commits messages are ignored by the tool when calculating next
| **`--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)) |
| **`--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)) |
| **`--commitMessageFormat`** | `string` | `undefined` | format the auto-generated message commit ([details](https://github.com/jscutlery/semver#commit-message-customization)) |
| **`--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 |
| **`--preset`** | `string \| object` | `'angular'` | customize Conventional Changelog options ([details](https://github.com/jscutlery/semver#customizing-conventional-changelog-options)) |

#### Overwrite default configuration
Expand Down
40 changes: 40 additions & 0 deletions packages/semver/src/executors/version/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ describe('@jscutlery/semver:version', () => {
project.updatePackageJson as jest.MockedFunction<
typeof project.updatePackageJson
>;
const mockUpdateCustomJsons =
project.updateCustomJsons as jest.MockedFunction<
typeof project.updateCustomJsons
>;
const mockUpdateChangelog = changelog.updateChangelog as jest.MockedFunction<
typeof changelog.updateChangelog
>;
Expand Down Expand Up @@ -107,6 +111,17 @@ describe('@jscutlery/semver:version', () => {
mockUpdatePackageJson.mockImplementation(({ projectRoot }) =>
of(project.getPackageJsonPath(projectRoot))
);
mockUpdateCustomJsons.mockImplementation(
({ projectRoot, customJsonPaths }) => {
const result: string[] = [];
if (customJsonPaths) {
for (const v of customJsonPaths) {
result.push('file:' + v.split(':')[0]);
}
}
return of(result);
}
);
mockCalculateChangelogChanges.mockReturnValue((source) => {
source.subscribe();
return of('');
Expand Down Expand Up @@ -150,6 +165,10 @@ describe('@jscutlery/semver:version', () => {
expect(mockUpdateChangelog).toHaveBeenCalledBefore(
mockUpdatePackageJson as jest.Mock
);
expect(mockUpdatePackageJson).toHaveBeenCalledBefore(
mockUpdateCustomJsons as jest.Mock
);

expect(mockCommit).toHaveBeenCalledBefore(mockCreateTag as jest.Mock);
expect(mockCreateTag).toHaveBeenCalledBefore(mockTryPush as jest.Mock);
expect(mockTryPush).toHaveBeenCalledBefore(mockRunPostTargets as jest.Mock);
Expand Down Expand Up @@ -784,4 +803,25 @@ describe('@jscutlery/semver:version', () => {
);
});
});

describe('--customJsonPaths', () => {
it('should use --customJsonPaths ', async () => {
const { success } = await version(
{ ...options, customJsonPaths: ['src/version.json:version'] },
context
);

expect(success).toBe(true);

expect(mockUpdateCustomJsons).toBeCalledWith(
expect.objectContaining({
customJsonPaths: ['src/version.json:version'],
})
);

expect(mockAddToStage).toHaveBeenLastCalledWith(
expect.objectContaining({ paths: ['file:src/version.json'] })
);
});
});
});
3 changes: 3 additions & 0 deletions packages/semver/src/executors/version/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default async function version(
allowEmptyRelease,
skipCommitTypes,
skipCommit,
customJsonPaths,
} = _normalizeOptions(options);
const workspaceRoot = context.root;
const projectName = context.projectName as string;
Expand Down Expand Up @@ -128,6 +129,7 @@ export default async function version(
changelogHeader,
workspaceRoot,
projectName,
customJsonPaths,
skipProjectChangelog,
commitMessage,
dependencyUpdates,
Expand Down Expand Up @@ -232,6 +234,7 @@ function _normalizeOptions(options: VersionBuilderSchema) {
versionTagPrefix: options.tagPrefix ?? options.versionTagPrefix,
commitMessageFormat: options.commitMessageFormat as string,
skipCommit: options.skipCommit as boolean,
customJsonPaths: options.customJsonPaths as string[],
preset:
options.preset === 'conventional'
? 'conventionalcommits'
Expand Down
1 change: 1 addition & 0 deletions packages/semver/src/executors/version/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface VersionBuilderSchema {
allowEmptyRelease?: boolean;
skipCommitTypes?: string[];
commitMessageFormat?: string;
customJsonPaths?: string[];
preset: Preset;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/semver/src/executors/version/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Step =
| 'warning'
| 'calculate_version_success'
| 'package_json_success'
| 'custom_json_success'
| 'changelog_success'
| 'tag_success'
| 'post_target_success'
Expand All @@ -22,6 +23,7 @@ const iconMap = new Map<Step, string>([
['changelog_success', '📜'],
['commit_success', '📦'],
['package_json_success', '📝'],
['custom_json_success', '📝'],
['post_target_success', '🎉'],
['tag_success', '🔖'],
['push_success', '🚀'],
Expand Down
157 changes: 156 additions & 1 deletion packages/semver/src/executors/version/utils/project.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import * as fs from 'fs';
import { lastValueFrom } from 'rxjs';

import { readPackageJson } from './project';
import {
readPackageJson,
updateCustomJson,
updateCustomJsons,
} from './project';
import { PathLike } from 'fs';
import { FileHandle } from 'fs/promises';
import { Stream } from 'stream';

const fsPromises = fs.promises;

Expand All @@ -15,3 +22,151 @@ describe('readPackageJson', () => {
});
});
});

describe('Update custom version into json', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('should update version in JSON content - variant 1', async () => {
jest.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
jest
.spyOn(fsPromises, 'readFile')
.mockResolvedValue(`{"info":{"version":"2.1.0"}}`);
jest
.spyOn(fsPromises, 'writeFile')
.mockImplementation(
async (
file: PathLike | FileHandle,
data:
| string
| NodeJS.ArrayBufferView
| Iterable<string | NodeJS.ArrayBufferView>
| AsyncIterable<string | NodeJS.ArrayBufferView>
| Stream
) => {
expect(data).toBe(`{"info":{"version":"1.2.3"}}\n`);
return;
}
);
const s = updateCustomJson({
newVersion: '1.2.3',
projectName: 'test',
dryRun: false,
projectRoot: 'test',
customJsonPath: 'src/version.json:info.version',
});
await lastValueFrom(s);
});

it('should return null on dryRun', async () => {
const s = updateCustomJson({
newVersion: '1.2.3',
projectName: 'test',
dryRun: true,
projectRoot: 'test',
customJsonPath: 'src/version.json:info.version',
});

const resp = await lastValueFrom(s);
expect(resp).toBe(null);
});

it('should return null if file is empty or does not exist', async () => {
jest.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
jest.spyOn(fsPromises, 'readFile').mockResolvedValue(``);

const s = updateCustomJson({
newVersion: '1.2.3',
projectName: 'test',
dryRun: false,
projectRoot: 'test',
customJsonPath: 'src/version.json:info.version',
});
const resp = await lastValueFrom(s);
expect(resp).toBe(null);
});

it('should return empty array on undefined customJsonPaths', async () => {
const s = updateCustomJsons({
newVersion: '1.2.3',
projectName: 'test',
dryRun: false,
projectRoot: 'test',
});

const resp = await lastValueFrom(s);
expect(resp).toBeArrayOfSize(0);
});

it('should update version in multiple JSON contents', async () => {
const result: string[] = [];
jest.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
jest
.spyOn(fsPromises, 'readFile')
.mockImplementation(async (path: PathLike | FileHandle) => {
if (path.toString().includes('file1.json')) {
return '{"version":"0.0.0"}';
}
if (path.toString().includes('file2.json')) {
return '{"info":{"version":"0.0.0"}}';
}
return '';
});
jest
.spyOn(fsPromises, 'writeFile')
.mockImplementation(
async (
file: PathLike | FileHandle,
data:
| string
| NodeJS.ArrayBufferView
| Iterable<string | NodeJS.ArrayBufferView>
| AsyncIterable<string | NodeJS.ArrayBufferView>
| Stream
) => {
if (file.toString().includes('file1.json')) {
result.push(data as string);
}
if (file.toString().includes('file2.json')) {
result.push(data as string);
}
}
);

const s = updateCustomJsons({
newVersion: '1.2.3',
projectName: 'test',
dryRun: false,
projectRoot: 'test',
customJsonPaths: [
'src/file1.json:version',
'src/file2.json:info.version',
],
});
await lastValueFrom(s);

expect(result).toContainAllValues([
'{"version":"1.2.3"}\n',
'{"info":{"version":"1.2.3"}}\n',
]);
});

it('should not touch file and should return empty array on dryRun', async () => {
const mock = jest.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
const s = updateCustomJsons({
newVersion: '1.2.3',
projectName: 'test',
dryRun: true,
projectRoot: 'test',
customJsonPaths: [
'src/file1.json:version',
'src/file2.json:info.version',
],
});

const resp = await lastValueFrom(s);
expect(mock).not.toBeCalled();
expect(resp).toBeArrayOfSize(0);
});
});
Loading

0 comments on commit 37f6454

Please sign in to comment.