Skip to content
Draft
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
17 changes: 9 additions & 8 deletions bin/create-release-branch.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env node

// Three things:
// - This file doesn't export anything, as it's a script.
// - We are using a `.js` extension because that's what appears in `dist/`.
// - This file will only exist after running `yarn build`. We don't want
// developers or CI to receive a lint error if the script has not been run.
// (A warning will appear if the script *has* been run, but that is okay.)
// eslint-disable-next-line import-x/no-unassigned-import, import-x/extensions, import-x/no-unresolved
import '../dist/cli.js';
// This file will only exist after running `yarn build`, and it will get a `.js`
// extension.
//
// We don't want developers or CI to receive a lint error if the script has not
// been run. (A warning will appear if the script *has* been run, but that is
// okay.)
//
// eslint-disable-next-line import-x/extensions, import-x/no-unassigned-import, import-x/no-unresolved
import '../dist/scripts/cli.js';
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const config = createConfig([

{
files: ['**/*.js', '**/*.cjs', '**/*.ts', '**/*.test.ts', '**/*.test.js'],
ignores: ['src/ui/**'],
ignores: ['src/ui/app/**'],
extends: nodejs,
},

Expand Down
2 changes: 1 addition & 1 deletion jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ module.exports = {
'/src/command-line-arguments.ts',
'/src/ui.ts',
'/src/ui/types.ts',
'/src/dirname.ts',
'/src/get-source-directory-path.ts',
],

// Indicates which provider should be used to instrument code for coverage
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@types/express": "^5.0.0",
"@types/jest": "^29.5.10",
"@types/jest-when": "^3.5.2",
"@types/node": "^17.0.23",
"@types/node": "^22.0.0",
"@types/prettier": "^2.7.3",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
Expand Down
18 changes: 0 additions & 18 deletions src/cli.ts

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import path from 'path';
import * as commandLineArgumentsModule from './command-line-arguments.js';
import * as envModule from './env.js';
import { determineInitialParameters } from './initial-parameters.js';
import * as projectModule from './project.js';
import {
buildMockProject,
buildMockPackage,
createNoopWriteStream,
} from '../tests/unit/helpers.js';
} from '../../tests/unit/helpers.js';
import * as projectModule from '../core/project.js';

jest.mock('../core/project');
jest.mock('./command-line-arguments');
jest.mock('./env');
jest.mock('./project');

describe('initial-parameters', () => {
describe('determineInitialParameters', () => {
Expand Down
16 changes: 3 additions & 13 deletions src/initial-parameters.ts → src/cli/initial-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,9 @@ import os from 'os';
import path from 'path';

import { readCommandLineArguments } from './command-line-arguments.js';
import { WriteStreamLike } from './fs.js';
import { readProject, Project } from './project.js';

/**
* 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';
import { WriteStreamLike } from '../core/fs.js';
import { readProject, Project } from '../core/project.js';
import { ReleaseType } from '../core/types.js';

/**
* Various pieces of information that the tool uses to run, derived from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,35 @@ import { when } from 'jest-when';
import path from 'path';
import { MockWritable } from 'stdio-mock';

import { determineEditor } from './editor.js';
import type { Editor } from './editor.js';
import { getEnvironmentVariables } from './env.js';
import { followMonorepoWorkflow } from './monorepo-workflow-operations.js';
import { Project } from './project.js';
import { executeReleasePlan, planRelease } from './release-plan.js';
import type { ReleasePlan } from './release-plan.js';
import { withSandbox, Sandbox, isErrorWithCode } from '../../tests/helpers.js';
import { buildMockProject, Require } from '../../tests/unit/helpers.js';
import { determineEditor } from '../core/editor.js';
import type { Editor } from '../core/editor.js';
import { Project } from '../core/project.js';
import { executeReleasePlan, planRelease } from '../core/release-plan.js';
import type { ReleasePlan } from '../core/release-plan.js';
import {
generateReleaseSpecificationTemplateForMonorepo,
waitForUserToEditReleaseSpecification,
validateReleaseSpecification,
} from './release-specification.js';
import type { ReleaseSpecification } from './release-specification.js';
import { commitAllChanges } from './repo.js';
import * as workflowOperationsModule from './workflow-operations.js';
} from '../core/release-specification.js';
import type { ReleaseSpecification } from '../core/release-specification.js';
import { commitAllChanges } from '../core/repo.js';
import * as workflowOperationsModule from '../core/workflow-operations.js';
import {
deduplicateDependencies,
fixConstraints,
updateYarnLockfile,
} from './yarn-commands.js';
import { withSandbox, Sandbox, isErrorWithCode } from '../tests/helpers.js';
import { buildMockProject, Require } from '../tests/unit/helpers.js';
} from '../core/yarn-commands.js';

jest.mock('./editor');
jest.mock('./release-plan');
jest.mock('./release-specification');
jest.mock('./repo');
jest.mock('./yarn-commands.js');
jest.mock('../core/editor');
jest.mock('../core/release-plan');
jest.mock('../core/release-specification');
jest.mock('../core/repo');
jest.mock('../core/yarn-commands.js');
jest.mock('./env');

const determineEditorMock = jest.mocked(determineEditor);
const generateReleaseSpecificationTemplateForMonorepoMock = jest.mocked(
Expand All @@ -47,6 +49,7 @@ const commitAllChangesMock = jest.mocked(commitAllChanges);
const fixConstraintsMock = jest.mocked(fixConstraints);
const updateYarnLockfileMock = jest.mocked(updateYarnLockfile);
const deduplicateDependenciesMock = jest.mocked(deduplicateDependencies);
const getEnvironmentVariablesMock = jest.mocked(getEnvironmentVariables);

/**
* Tests the given path to determine whether it represents a file.
Expand Down Expand Up @@ -249,6 +252,8 @@ async function setupFollowMonorepoWorkflow({
.calledWith(projectDirectoryPath, '')
.mockResolvedValue();

getEnvironmentVariablesMock.mockReturnValue({});

if (doesReleaseSpecFileExist) {
await fs.promises.writeFile(
releaseSpecificationPath,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import type { WriteStream } from 'fs';
import path from 'path';

import { determineEditor } from './editor.js';
import { getEnvironmentVariables } from './env.js';
import { determineEditor } from '../core/editor.js';
import {
ensureDirectoryPathExists,
fileExists,
removeFile,
writeFile,
} from './fs.js';
import { ReleaseType } from './initial-parameters.js';
} from '../core/fs.js';
import {
Project,
updateChangelogsForChangedPackages,
restoreChangelogsForSkippedPackages,
} from './project.js';
import { planRelease, executeReleasePlan } from './release-plan.js';
} from '../core/project.js';
import { planRelease, executeReleasePlan } from '../core/release-plan.js';
import {
generateReleaseSpecificationTemplateForMonorepo,
waitForUserToEditReleaseSpecification,
validateReleaseSpecification,
} from './release-specification.js';
import { commitAllChanges } from './repo.js';
import { createReleaseBranch } from './workflow-operations.js';
} from '../core/release-specification.js';
import { commitAllChanges } from '../core/repo.js';
import { ReleaseType } from '../core/types.js';
import { createReleaseBranch } from '../core/workflow-operations.js';
import {
deduplicateDependencies,
fixConstraints,
updateYarnLockfile,
} from './yarn-commands.js';
} from '../core/yarn-commands.js';

/**
* For a monorepo, the process works like this:
Expand Down Expand Up @@ -104,7 +105,8 @@ export async function followMonorepoWorkflow({
'Release spec already exists. Picking back up from previous run.\n',
);
} else {
const editor = await determineEditor();
const { EDITOR } = getEnvironmentVariables();
const editor = await determineEditor(EDITOR);

const releaseSpecificationTemplate =
await generateReleaseSpecificationTemplateForMonorepo({
Expand Down
12 changes: 7 additions & 5 deletions src/main.ts → src/cli/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import type { WriteStream } from 'fs';

import { determineInitialParameters } from './initial-parameters.js';
import { followMonorepoWorkflow } from './monorepo-workflow-operations.js';
import { startUI } from './ui.js';
import { start as startUI } from '../ui/start.js';

/**
* The main function for this tool. Designed to not access `process.argv`,
* `process.env`, `process.cwd()`, `process.stdout`, or `process.stderr`
* directly so as to be more easily testable.
* The entrypoint for this tool.
*
* Designed to not access `process.argv`, `process.env`, `process.cwd()`,
* `process.stdout`, or `process.stderr` directly so as to be more easily
* testable.
*
* @param args - The arguments.
* @param args.argv - The name of this executable and its arguments (as obtained
Expand All @@ -16,7 +18,7 @@ import { startUI } from './ui.js';
* @param args.stdout - A stream that can be used to write to standard out.
* @param args.stderr - A stream that can be used to write to standard error.
*/
export async function main({
export async function run({
argv,
cwd,
stdout,
Expand Down
26 changes: 13 additions & 13 deletions src/main.test.ts → src/cli/start.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import fs from 'fs';

import * as initialParametersModule from './initial-parameters.js';
import { main } from './main.js';
import * as monorepoWorkflowOperations from './monorepo-workflow-operations.js';
import * as ui from './ui.js';
import { buildMockProject } from '../tests/unit/helpers.js';
import { run } from './run.js';
import { buildMockProject } from '../../tests/unit/helpers.js';
import * as ui from '../ui/start.js';

jest.mock('../core/get-root-directory-path', () => ({
getRootDirectoryPath: jest.fn().mockReturnValue('/path/to/somewhere'),
}));
jest.mock('../ui/start');
jest.mock('./initial-parameters');
jest.mock('./monorepo-workflow-operations');
jest.mock('./ui');
jest.mock('./dirname', () => ({
getCurrentDirectoryPath: jest.fn().mockReturnValue('/path/to/somewhere'),
}));
jest.mock('open', () => ({
apps: {
browser: jest.fn(),
},
}));

describe('main', () => {
describe('start', () => {
it('executes the CLI monorepo workflow if the project is a monorepo and interactive is false', async () => {
const project = buildMockProject({ isMonorepo: true });
const stdout = fs.createWriteStream('/dev/null');
Expand All @@ -38,7 +38,7 @@ describe('main', () => {
.spyOn(monorepoWorkflowOperations, 'followMonorepoWorkflow')
.mockResolvedValue();

await main({
await run({
argv: [],
cwd: '/path/to/somewhere',
stdout,
Expand Down Expand Up @@ -71,16 +71,16 @@ describe('main', () => {
interactive: true,
port: 3000,
});
const startUISpy = jest.spyOn(ui, 'startUI').mockResolvedValue();
const startSpy = jest.spyOn(ui, 'start').mockResolvedValue();

await main({
await run({
argv: [],
cwd: '/path/to/somewhere',
stdout,
stderr,
});

expect(startUISpy).toHaveBeenCalledWith({
expect(startSpy).toHaveBeenCalledWith({
project,
releaseType: 'backport',
defaultBranch: 'main',
Expand Down Expand Up @@ -109,7 +109,7 @@ describe('main', () => {
.spyOn(monorepoWorkflowOperations, 'followMonorepoWorkflow')
.mockResolvedValue();

await main({
await run({
argv: [],
cwd: '/path/to/somewhere',
stdout,
Expand Down
Loading
Loading