Skip to content

Commit

Permalink
feat: ✅ add --sync-versions option
Browse files Browse the repository at this point in the history
Closes #5
  • Loading branch information
yjaaidi committed Dec 23, 2020
1 parent 648b711 commit 2fdf2c3
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 89 deletions.
232 changes: 151 additions & 81 deletions packages/semver/src/builders/version/builder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as childProcess from '@lerna/child-process';
import { MockBuilderContext } from '@nrwl/workspace/testing';
import * as fs from 'fs';
import * as standardVersion from 'standard-version';

import { getMockContext } from '../../utils/testing';
import { runBuilder } from './builder';
import { VersionBuilderSchema } from './schema';
Expand All @@ -21,89 +21,159 @@ const options: VersionBuilderSchema = {
describe('@jscutlery/semver:version', () => {
let context: MockBuilderContext;

beforeEach(async () => {
context = await getMockContext();
context.logger.error = jest.fn();
context.getProjectMetadata = jest
.fn()
.mockResolvedValue({ root: '/root/lib' });
beforeEach(() => {
jest
.spyOn(fs, 'readFile')
.mockImplementation((...args: Parameters<typeof fs.readFile>) => {
const callback = args[args.length - 1] as Function;
callback(
null,
JSON.stringify({
version: 1,
projects: {
a: {
root: 'packages/a',
},
b: {
root: 'packages/b',
},
},
})
);
});
});

it('runs standard-version with project options', async () => {
const output = await runBuilder(options, context).toPromise();

expect(output).toEqual(expect.objectContaining({ success: true }));
expect(standardVersion).toBeCalledWith(
expect.objectContaining({
silent: false,
preset: expect.stringContaining('conventional-changelog-angular'),
dryRun: false,
noVerify: false,
firstRelease: false,
path: '/root/lib',
infile: '/root/lib/CHANGELOG.md',
bumpFiles: ['/root/lib/package.json'],
packageFiles: ['/root/lib/package.json'],
})
);
describe('Independent version', () => {
beforeEach(async () => {
context = await getMockContext();
context.logger.error = jest.fn();
context.getProjectMetadata = jest
.fn()
.mockResolvedValue({ root: '/root/packages/lib' });
});
it('runs standard-version with project options', async () => {
const output = await runBuilder(options, context).toPromise();

expect(output).toEqual(expect.objectContaining({ success: true }));
expect(standardVersion).toBeCalledWith(
expect.objectContaining({
silent: false,
preset: expect.stringContaining('conventional-changelog-angular'),
dryRun: false,
noVerify: false,
firstRelease: false,
path: '/root/packages/lib',
infile: '/root/packages/lib/CHANGELOG.md',
bumpFiles: ['/root/packages/lib/package.json'],
packageFiles: ['/root/packages/lib/package.json'],
})
);
});

it('should not push to Git by default', async () => {
await runBuilder(options, context).toPromise();

expect(childProcess.exec).not.toHaveBeenCalled();
});

it('should push to Git with right options', async () => {
await runBuilder(
{ ...options, push: true, remote: 'origin', baseBranch: 'main' },
context
).toPromise();

expect(childProcess.exec).toHaveBeenCalledWith(
'git',
expect.arrayContaining([
'push',
'--follow-tags',
'--atomic',
'origin',
'main',
])
);
});

it(`should push to Git and add '--no-verify' option when asked for`, async () => {
await runBuilder(
{
...options,
push: true,
remote: 'origin',
baseBranch: 'main',
noVerify: true,
},
context
).toPromise();

expect(childProcess.exec).toHaveBeenCalledWith(
'git',
expect.arrayContaining([
'push',
'--follow-tags',
'--no-verify',
'--atomic',
'origin',
'main',
])
);
});

it('should fail if Git config is missing', async () => {
const output = await runBuilder(
{ ...options, push: true, remote: undefined, baseBranch: undefined },
context
).toPromise();

expect(context.logger.error).toBeCalled();
expect(output).toEqual(expect.objectContaining({ success: false }));
});
});

it('should not push to Git by default', async () => {
await runBuilder(options, context).toPromise();

expect(childProcess.exec).not.toHaveBeenCalled();
});

it('should push to Git with right options', async () => {
await runBuilder(
{ ...options, push: true, remote: 'origin', baseBranch: 'main' },
context
).toPromise();

expect(childProcess.exec).toHaveBeenCalledWith(
'git',
expect.arrayContaining([
'push',
'--follow-tags',
'--atomic',
'origin',
'main',
])
);
});

it(`should push to Git and add '--no-verify' option when asked for`, async () => {
await runBuilder(
{
...options,
push: true,
remote: 'origin',
baseBranch: 'main',
noVerify: true,
},
context
).toPromise();

expect(childProcess.exec).toHaveBeenCalledWith(
'git',
expect.arrayContaining([
'push',
'--follow-tags',
'--no-verify',
'--atomic',
'origin',
'main',
])
);
});

it('should fail if Git config is missing', async () => {
const output = await runBuilder(
{ ...options, push: true, remote: undefined, baseBranch: undefined },
context
).toPromise();

expect(context.logger.error).toBeCalled()
expect(output).toEqual(expect.objectContaining({ success: false }));
describe('Sync version', () => {
beforeEach(async () => {
context = await getMockContext();

/* With the sync version, the builder runs on the workspace. */
context.getProjectMetadata = jest
.fn()
.mockResolvedValue({ root: '/root' });
});

it('should sync projects versions', async () => {
const output = await runBuilder(
{
...options,
push: true,
remote: undefined,
baseBranch: undefined,
/* Enable sync versions. */
syncVersions: true,
},
context
).toPromise();

expect(fs.readFile).toHaveBeenLastCalledWith(
'/root/workspace.json',
'utf-8',
expect.any(Function)
);
expect(standardVersion).toBeCalledWith(
expect.objectContaining({
silent: false,
preset: expect.stringContaining('conventional-changelog-angular'),
dryRun: false,
noVerify: false,
firstRelease: false,
path: '/root',
infile: '/root/CHANGELOG.md',
bumpFiles: [
'/root/packages/a/package.json',
'/root/packages/b/package.json',
],
packageFiles: ['/root/package.json'],
})
);
});
});
});
64 changes: 56 additions & 8 deletions packages/semver/src/builders/version/builder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import {
BuilderContext,
BuilderOutput,
createBuilder,
} from '@angular-devkit/architect';
import { noop } from '@angular-devkit/schematics';
import { exec } from '@lerna/child-process';
import { readFile } from 'fs';
import { resolve } from 'path';
import { defer, from, Observable, of, throwError } from 'rxjs';
import { catchError, mapTo, switchMap, switchMapTo } from 'rxjs/operators';
import { catchError, map, mapTo, switchMap, switchMapTo } from 'rxjs/operators';
import * as standardVersion from 'standard-version';

import { promisify } from 'util';
import { VersionBuilderSchema } from './schema';

async function getProjectRoot(context: BuilderContext): Promise<string> {
Expand Down Expand Up @@ -94,22 +99,39 @@ export function runBuilder(
options: VersionBuilderSchema,
context: BuilderContext
): Observable<BuilderOutput> {
const { push, remote, dryRun, baseBranch, noVerify, firstRelease } = options;
const {
push,
remote,
dryRun,
baseBranch,
noVerify,
firstRelease,
syncVersions,
} = options;

return from(getProjectRoot(context)).pipe(
switchMap((projectRoot) =>
standardVersion({
getPackageFiles(projectRoot).pipe(
map((packageFiles) => ({ projectRoot, packageFiles }))
)
),
switchMap(({ projectRoot, packageFiles }) => {
const bumpFiles = syncVersions
? packageFiles
: [resolve(projectRoot, 'package.json')];

return standardVersion({
silent: false,
path: projectRoot,
dryRun,
noVerify,
firstRelease,
infile: resolve(projectRoot, 'CHANGELOG.md'),
packageFiles: [resolve(projectRoot, 'package.json')],
bumpFiles: [resolve(projectRoot, 'package.json')],
bumpFiles,
preset: require.resolve('conventional-changelog-angular'),
})
),
});
}),
push && dryRun === false
? switchMapTo(
tryPushingToGitRemote({
Expand All @@ -128,4 +150,30 @@ export function runBuilder(
);
}

export interface WorkspaceDefinition {
projects: {
[key: string]: {
root: string;
};
};
}

export function getPackageFiles(projectRoot: string): Observable<string[]> {
return getWorkspaceDefinition(projectRoot).pipe(
map((workspaceDefinition) =>
Object.values(workspaceDefinition.projects).map((project) =>
resolve(projectRoot, project.root, 'package.json')
)
)
);
}

export function getWorkspaceDefinition(
projectRoot: string
): Observable<WorkspaceDefinition> {
return from(
promisify(readFile)(resolve(projectRoot, 'workspace.json'), 'utf-8')
).pipe(map((data) => JSON.parse(data)));
}

export default createBuilder(runBuilder);
1 change: 1 addition & 0 deletions packages/semver/src/builders/version/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export interface VersionBuilderSchema extends JsonObject {
push?: boolean;
remote?: string;
baseBranch?: string;
syncVersions?: boolean;
}
5 changes: 5 additions & 0 deletions packages/semver/src/builders/version/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
"description": "Pushes against git base branch.",
"type": "string",
"default": "main"
},
"syncVersions": {
"description": "Sync all package versions.",
"type": "boolean",
"default": false
}
},
"required": []
Expand Down

0 comments on commit 2fdf2c3

Please sign in to comment.