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: option to specify additional json files to update version number #649

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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