Skip to content
Merged
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
7 changes: 7 additions & 0 deletions src/command-line-arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface CommandLineArguments {
projectDirectory: string;
tempDirectory: string | undefined;
reset: boolean;
backport: boolean;
}

/**
Expand Down Expand Up @@ -37,6 +38,12 @@ export async function readCommandLineArguments(
type: 'boolean',
default: false,
})
.option('backport', {
describe:
'Instructs the tool to bump the second part of the version rather than the first for a backport release.',
type: 'boolean',
default: false,
})
.help()
.strict()
.parse();
Expand Down
114 changes: 113 additions & 1 deletion src/functional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { buildChangelog } from '../tests/functional/helpers/utils';

describe('create-release-branch (functional)', () => {
describe('against a monorepo with independent versions', () => {
it('updates the version of the root package to be the current date along with the versions of the specified packages', async () => {
it('bumps the ordinary part of the root package and updates the versions of the specified packages according to the release spec', async () => {
await withMonorepoProjectEnvironment(
{
packages: {
Expand Down Expand Up @@ -114,6 +114,118 @@ describe('create-release-branch (functional)', () => {
);
});

it('bumps the backport part of the root package and updates the versions of the specified packages according to the release spec if --backport is provided', async () => {
await withMonorepoProjectEnvironment(
{
packages: {
$root$: {
name: '@scope/monorepo',
version: '1.0.0',
directoryPath: '.',
},
a: {
name: '@scope/a',
version: '0.1.2',
directoryPath: 'packages/a',
},
b: {
name: '@scope/b',
version: '1.1.4',
directoryPath: 'packages/b',
},
c: {
name: '@scope/c',
version: '2.0.13',
directoryPath: 'packages/c',
},
d: {
name: '@scope/d',
version: '1.2.3',
directoryPath: 'packages/d',
},
},
workspaces: {
'.': ['packages/*'],
},
},
async (environment) => {
await environment.updateJsonFile('package.json', {
scripts: {
foo: 'bar',
},
});
await environment.updateJsonFileWithinPackage('a', 'package.json', {
scripts: {
foo: 'bar',
},
});
await environment.updateJsonFileWithinPackage('b', 'package.json', {
scripts: {
foo: 'bar',
},
});
await environment.updateJsonFileWithinPackage('c', 'package.json', {
scripts: {
foo: 'bar',
},
});
await environment.updateJsonFileWithinPackage('d', 'package.json', {
scripts: {
foo: 'bar',
},
});

await environment.runTool({
args: ['--backport'],
releaseSpecification: {
packages: {
a: 'major',
b: 'minor',
c: 'patch',
d: '1.2.4',
},
},
});

expect(await environment.readJsonFile('package.json')).toStrictEqual({
name: '@scope/monorepo',
version: '1.1.0',
private: true,
workspaces: ['packages/*'],
scripts: { foo: 'bar' },
});
expect(
await environment.readJsonFileWithinPackage('a', 'package.json'),
).toStrictEqual({
name: '@scope/a',
version: '1.0.0',
scripts: { foo: 'bar' },
});
expect(
await environment.readJsonFileWithinPackage('b', 'package.json'),
).toStrictEqual({
name: '@scope/b',
version: '1.2.0',
scripts: { foo: 'bar' },
});
expect(
await environment.readJsonFileWithinPackage('c', 'package.json'),
).toStrictEqual({
name: '@scope/c',
version: '2.0.14',
scripts: { foo: 'bar' },
});
expect(
await environment.readJsonFileWithinPackage('d', 'package.json'),
).toStrictEqual({
name: '@scope/d',
version: '1.2.4',
scripts: { foo: 'bar' },
});
},
);
});

it("updates each of the specified packages' changelogs by adding a new section which lists all commits concerning the package over the entire history of the repo", async () => {
await withMonorepoProjectEnvironment(
{
Expand Down
83 changes: 73 additions & 10 deletions src/initial-parameters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('initial-parameters', () => {
projectDirectory: '/path/to/project',
tempDirectory: '/path/to/temp',
reset: true,
backport: false,
});
jest
.spyOn(envModule, 'getEnvironmentVariables')
Expand All @@ -42,16 +43,17 @@ describe('initial-parameters', () => {
.calledWith('/path/to/project', { stderr })
.mockResolvedValue(project);

const config = await determineInitialParameters({
const initialParameters = await determineInitialParameters({
argv: ['arg1', 'arg2'],
cwd: '/path/to/somewhere',
stderr,
});

expect(config).toStrictEqual({
expect(initialParameters).toStrictEqual({
project,
tempDirectoryPath: '/path/to/temp',
reset: true,
releaseType: 'ordinary',
});
});

Expand All @@ -66,6 +68,7 @@ describe('initial-parameters', () => {
projectDirectory: 'project',
tempDirectory: undefined,
reset: true,
backport: false,
});
jest
.spyOn(envModule, 'getEnvironmentVariables')
Expand Down Expand Up @@ -94,6 +97,7 @@ describe('initial-parameters', () => {
projectDirectory: '/path/to/project',
tempDirectory: 'tmp',
reset: true,
backport: false,
});
jest
.spyOn(envModule, 'getEnvironmentVariables')
Expand All @@ -102,13 +106,15 @@ describe('initial-parameters', () => {
.calledWith('/path/to/project', { stderr })
.mockResolvedValue(project);

const config = await determineInitialParameters({
const initialParameters = await determineInitialParameters({
argv: ['arg1', 'arg2'],
cwd: '/path/to/cwd',
stderr,
});

expect(config.tempDirectoryPath).toStrictEqual('/path/to/cwd/tmp');
expect(initialParameters.tempDirectoryPath).toStrictEqual(
'/path/to/cwd/tmp',
);
});

it('uses a default temporary directory based on the name of the package if no temporary directory was given', async () => {
Expand All @@ -122,6 +128,7 @@ describe('initial-parameters', () => {
projectDirectory: '/path/to/project',
tempDirectory: undefined,
reset: true,
backport: false,
});
jest
.spyOn(envModule, 'getEnvironmentVariables')
Expand All @@ -130,13 +137,13 @@ describe('initial-parameters', () => {
.calledWith('/path/to/project', { stderr })
.mockResolvedValue(project);

const config = await determineInitialParameters({
const initialParameters = await determineInitialParameters({
argv: ['arg1', 'arg2'],
cwd: '/path/to/cwd',
stderr,
});

expect(config.tempDirectoryPath).toStrictEqual(
expect(initialParameters.tempDirectoryPath).toStrictEqual(
path.join(os.tmpdir(), 'create-release-branch', '@foo__bar'),
);
});
Expand All @@ -150,6 +157,7 @@ describe('initial-parameters', () => {
projectDirectory: '/path/to/project',
tempDirectory: '/path/to/temp',
reset: true,
backport: false,
});
jest
.spyOn(envModule, 'getEnvironmentVariables')
Expand All @@ -158,13 +166,13 @@ describe('initial-parameters', () => {
.calledWith('/path/to/project', { stderr })
.mockResolvedValue(project);

const config = await determineInitialParameters({
const initialParameters = await determineInitialParameters({
argv: ['arg1', 'arg2'],
cwd: '/path/to/somewhere',
stderr,
});

expect(config.reset).toBe(true);
expect(initialParameters.reset).toBe(true);
});

it('returns initial parameters including reset: false, derived from a command-line argument of "--reset false"', async () => {
Expand All @@ -176,6 +184,61 @@ describe('initial-parameters', () => {
projectDirectory: '/path/to/project',
tempDirectory: '/path/to/temp',
reset: false,
backport: false,
});
jest
.spyOn(envModule, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: undefined });
when(jest.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { stderr })
.mockResolvedValue(project);

const initialParameters = await determineInitialParameters({
argv: ['arg1', 'arg2'],
cwd: '/path/to/somewhere',
stderr,
});

expect(initialParameters.reset).toBe(false);
});

it('returns initial parameters including a releaseType of "backport", derived from a command-line argument of "--backport true"', async () => {
const project = buildMockProject();
const stderr = createNoopWriteStream();
when(jest.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.mockResolvedValue({
projectDirectory: '/path/to/project',
tempDirectory: '/path/to/temp',
reset: false,
backport: true,
});
jest
.spyOn(envModule, 'getEnvironmentVariables')
.mockReturnValue({ EDITOR: undefined });
when(jest.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { stderr })
.mockResolvedValue(project);

const initialParameters = await determineInitialParameters({
argv: ['arg1', 'arg2'],
cwd: '/path/to/somewhere',
stderr,
});

expect(initialParameters.releaseType).toBe('backport');
});

it('returns initial parameters including a releaseType of "ordinary", derived from a command-line argument of "--backport false"', async () => {
const project = buildMockProject();
const stderr = createNoopWriteStream();
when(jest.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.mockResolvedValue({
projectDirectory: '/path/to/project',
tempDirectory: '/path/to/temp',
reset: false,
backport: false,
});
jest
.spyOn(envModule, 'getEnvironmentVariables')
Expand All @@ -184,13 +247,13 @@ describe('initial-parameters', () => {
.calledWith('/path/to/project', { stderr })
.mockResolvedValue(project);

const config = await determineInitialParameters({
const initialParameters = await determineInitialParameters({
argv: ['arg1', 'arg2'],
cwd: '/path/to/somewhere',
stderr,
});

expect(config.reset).toBe(false);
expect(initialParameters.releaseType).toBe('ordinary');
});
});
});
27 changes: 22 additions & 5 deletions src/initial-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ import { readCommandLineArguments } from './command-line-arguments';
import { WriteStreamLike } from './fs';
import { readProject, Project } from './project';

/**
* The type of release being created as determined by the parent release.
*
* - An *ordinary* release includes features or fixes applied against the
* latest release and is designated by bumping the first part of that release's
* version string.
* - A *backport* release includes fixes applied against a previous release and
* is designated by bumping the second part of that release's version string.
*/
export type ReleaseType = 'ordinary' | 'backport';

interface InitialParameters {
project: Project;
tempDirectoryPath: string;
reset: boolean;
releaseType: ReleaseType;
}

/**
Expand All @@ -29,18 +41,23 @@ export async function determineInitialParameters({
cwd: string;
stderr: WriteStreamLike;
}): Promise<InitialParameters> {
const inputs = await readCommandLineArguments(argv);
const args = await readCommandLineArguments(argv);

const projectDirectoryPath = path.resolve(cwd, inputs.projectDirectory);
const projectDirectoryPath = path.resolve(cwd, args.projectDirectory);
const project = await readProject(projectDirectoryPath, { stderr });
const tempDirectoryPath =
inputs.tempDirectory === undefined
args.tempDirectory === undefined
? path.join(
os.tmpdir(),
'create-release-branch',
project.rootPackage.validatedManifest.name.replace('/', '__'),
)
: path.resolve(cwd, inputs.tempDirectory);
: path.resolve(cwd, args.tempDirectory);

return { project, tempDirectoryPath, reset: inputs.reset };
return {
project,
tempDirectoryPath,
reset: args.reset,
releaseType: args.backport ? 'backport' : 'ordinary',
};
}
Loading