Skip to content
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 packages/create-release-branch/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Show colors and progress spinners in the CLI output ([#154](https://github.com/MetaMask/release-tools/pull/154))
- Show the CLI output in one block, with a spinner for long steps and the output of a failing Yarn step ([#159](https://github.com/MetaMask/release-tools/pull/159))
- Show errors without a stack trace (set `DEBUG=create-release-branch:*` to see it), with their causes, and list release spec problems by line and package ([#160](https://github.com/MetaMask/release-tools/pull/160))
- Group repeated warnings (packages without a tag, changelogs without updates) into one warning with a list of packages ([#162](https://github.com/MetaMask/release-tools/pull/162))

### Fixed

Expand Down
43 changes: 10 additions & 33 deletions packages/create-release-branch/src/cli/initial-parameters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
buildMockProject,
buildMockPackage,
createNoopCliOutput,
} from '../../tests/unit/helpers.js';
import * as projectModule from '../project/project.js';
import * as envModule from '../utils/env.js';
Expand All @@ -28,7 +27,6 @@ describe('initial-parameters', () => {

it('returns an object derived from command-line arguments and environment variables that contains data necessary to run the workflow', async () => {
const project = buildMockProject();
const output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -45,13 +43,12 @@ describe('initial-parameters', () => {
EDITOR: undefined,
});
vi.when(vi.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { output })
.calledWith('/path/to/project')
.thenResolve(project);

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

expect(initialParameters).toStrictEqual({
Expand All @@ -71,7 +68,6 @@ describe('initial-parameters', () => {
const project = buildMockProject({
rootPackage: buildMockPackage(),
});
const output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -94,17 +90,13 @@ describe('initial-parameters', () => {
await determineInitialParameters({
argv: ['arg1', 'arg2'],
cwd: '/path/to/cwd',
output,
});

expect(readProjectSpy).toHaveBeenCalledWith('/path/to/cwd/project', {
output,
});
expect(readProjectSpy).toHaveBeenCalledWith('/path/to/cwd/project');
});

it('resolves the given temporary directory relative to the current working directory', async () => {
const project = buildMockProject();
const output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -121,13 +113,12 @@ describe('initial-parameters', () => {
EDITOR: undefined,
});
vi.when(vi.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { output })
.calledWith('/path/to/project')
.thenResolve(project);

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

expect(initialParameters.tempDirectoryPath).toBe('/path/to/cwd/tmp');
Expand All @@ -137,7 +128,6 @@ describe('initial-parameters', () => {
const project = buildMockProject({
rootPackage: buildMockPackage('@foo/bar'),
});
const output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -154,13 +144,12 @@ describe('initial-parameters', () => {
EDITOR: undefined,
});
vi.when(vi.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { output })
.calledWith('/path/to/project')
.thenResolve(project);

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

expect(initialParameters.tempDirectoryPath).toStrictEqual(
Expand All @@ -170,7 +159,6 @@ describe('initial-parameters', () => {

it('returns initial parameters including reset: true, derived from a command-line argument of "--reset true"', async () => {
const project = buildMockProject();
const output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -187,21 +175,19 @@ describe('initial-parameters', () => {
EDITOR: undefined,
});
vi.when(vi.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { output })
.calledWith('/path/to/project')
.thenResolve(project);

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

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

it('returns initial parameters including reset: false, derived from a command-line argument of "--reset false"', async () => {
const project = buildMockProject();
const output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -218,21 +204,19 @@ describe('initial-parameters', () => {
EDITOR: undefined,
});
vi.when(vi.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { output })
.calledWith('/path/to/project')
.thenResolve(project);

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

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 output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -249,21 +233,19 @@ describe('initial-parameters', () => {
EDITOR: undefined,
});
vi.when(vi.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { output })
.calledWith('/path/to/project')
.thenResolve(project);

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

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 output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -280,21 +262,19 @@ describe('initial-parameters', () => {
EDITOR: undefined,
});
vi.when(vi.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { output })
.calledWith('/path/to/project')
.thenResolve(project);

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

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

it('returns initial parameters including a frontEndName of "ui", derived from a command-line argument of "--interactive true"', async () => {
const project = buildMockProject();
const output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -311,21 +291,19 @@ describe('initial-parameters', () => {
EDITOR: undefined,
});
vi.when(vi.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { output })
.calledWith('/path/to/project')
.thenResolve(project);

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

expect(initialParameters.frontEndName).toBe('ui');
});

it('returns initial parameters including a frontEndName of "editor", derived from a command-line argument of "--interactive false"', async () => {
const project = buildMockProject();
const output = createNoopCliOutput();
vi.when(vi.spyOn(commandLineArgumentsModule, 'readCommandLineArguments'))
.calledWith(['arg1', 'arg2'])
.thenResolve({
Expand All @@ -342,13 +320,12 @@ describe('initial-parameters', () => {
EDITOR: undefined,
});
vi.when(vi.spyOn(projectModule, 'readProject'))
.calledWith('/path/to/project', { output })
.calledWith('/path/to/project')
.thenResolve(project);

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

expect(initialParameters.frontEndName).toBe('editor');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os from 'os';
import path from 'path';

import type { CliOutput } from '../core/cli-output.js';
import type { FrontEndName } from '../core/front-end.js';
import type { ModeName } from '../core/mode.js';
import type { Formatter, ReleaseType } from '../core/types.js';
Expand All @@ -27,22 +26,19 @@ type InitialParameters = {
* @param args - The arguments to this function.
* @param args.argv - The arguments to this executable.
* @param args.cwd - The directory in which this executable was run.
* @param args.output - Where to write user-facing messages.
* @returns The initial parameters.
*/
export async function determineInitialParameters({
argv,
cwd,
output,
}: {
argv: string[];
cwd: string;
output: CliOutput;
}): Promise<InitialParameters> {
const args = await readCommandLineArguments(argv);

const projectDirectoryPath = path.resolve(cwd, args.projectDirectory);
const project = await readProject(projectDirectoryPath, { output });
const project = await readProject(projectDirectoryPath);
const tempDirectoryPath =
args.tempDirectory === undefined
? path.join(
Expand Down
9 changes: 2 additions & 7 deletions packages/create-release-branch/src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,9 @@ export async function main({
frontEndName,
port,
formatter,
} = await determineInitialParameters({ argv, cwd, output });
} = await determineInitialParameters({ argv, cwd });

const mode = selectMode(modeName, {
project,
defaultBranch,
formatter,
output,
});
const mode = selectMode(modeName, { project, defaultBranch, formatter });
const frontEnd = selectFrontEnd(frontEndName, {
tempDirectoryPath,
reset,
Expand Down
8 changes: 1 addition & 7 deletions packages/create-release-branch/src/cli/select.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { describe, expect, it, vi } from 'vitest';

import {
buildMockProject,
createNoopCliOutput,
} from '../../tests/unit/helpers.js';
import { buildMockProject } from '../../tests/unit/helpers.js';
import type { FrontEnd } from '../core/front-end.js';
import type { Mode } from '../core/mode.js';
import * as editorFrontEndModule from '../frontends/editor/editor-front-end.js';
Expand All @@ -19,7 +16,6 @@ describe('select', () => {
describe('selectMode', () => {
it('selectMode returns the legacy mode', () => {
const project = buildMockProject();
const output = createNoopCliOutput();
const legacyMode = { name: 'legacy' } as Mode<never>;
const createLegacyModeSpy = vi
.spyOn(legacyModeModule, 'createLegacyMode')
Expand All @@ -29,14 +25,12 @@ describe('select', () => {
project,
defaultBranch: 'main',
formatter: 'prettier',
output,
});

expect(createLegacyModeSpy).toHaveBeenCalledWith({
project,
defaultBranch: 'main',
formatter: 'prettier',
output,
});
expect(mode).toBe(legacyMode);
});
Expand Down
3 changes: 0 additions & 3 deletions packages/create-release-branch/src/cli/select.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { assertExhaustive } from '@metamask/utils';

import type { CliOutput } from '../core/cli-output.js';
import type { FrontEnd, FrontEndName } from '../core/front-end.js';
import type { Mode, ModeName } from '../core/mode.js';
import type { Formatter } from '../core/types.js';
Expand All @@ -17,7 +16,6 @@ import type { Project } from '../project/project.js';
* @param options.project - Information about the project.
* @param options.defaultBranch - The name of the default branch.
* @param options.formatter - The formatter for the changelogs.
* @param options.output - Where to write user-facing messages.
* @returns The mode.
*/
export function selectMode(
Expand All @@ -26,7 +24,6 @@ export function selectMode(
project: Project;
defaultBranch: string;
formatter: Formatter;
output: CliOutput;
},
): Mode<unknown> {
switch (name) {
Expand Down
36 changes: 36 additions & 0 deletions packages/create-release-branch/src/core/cli-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,42 @@ describe('cli-output', () => {
expect(stdout.data()).toStrictEqual([]);
});

it('warnList writes the title and indented items to stderr', () => {
const stdout = new MockWritable();
const plainStderr = new MockWritable();
const styledStderr = buildTerminalStream();

createCliOutput({ stdout, stderr: plainStderr }).warnList('Some title:', [
'a',
'b',
]);
createCliOutput({ stdout, stderr: styledStderr }).warnList(
'Some title:',
['a'],
);

expect(writtenText(plainStderr)).toBe(
`${logLine(S_WARN, 'Some title:')}${S_BAR} a\n${S_BAR} b\n`,
);
expect(writtenText(styledStderr)).toContain(
` ${styleText('dim', 'a', { validateStream: false })}`,
);
expect(stdout.data()).toStrictEqual([]);
});

it('warnList indents every line of a multi-line item', () => {
const stdout = new MockWritable();
const stderr = new MockWritable();

createCliOutput({ stdout, stderr }).warnList('Some title:', [
'Line 1\nLine 2',
]);

expect(writtenText(stderr)).toBe(
`${logLine(S_WARN, 'Some title:')}${S_BAR} Line 1\n${S_BAR} Line 2\n`,
);
});

it('writes stderr without colors when only stdout is a color terminal', () => {
const stdout = buildTerminalStream();
const stderr = new MockWritable();
Expand Down
Loading
Loading