Skip to content

Commit

Permalink
Reorder workflow to update changelogs first
Browse files Browse the repository at this point in the history
  • Loading branch information
cryptodev-2s committed Nov 16, 2023
1 parent d3556b3 commit ddbb6da
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 26 deletions.
16 changes: 13 additions & 3 deletions src/monorepo-workflow-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import { determineEditor } from './editor';
import { ReleaseType } from './initial-parameters';
import { Project } from './project';
import { planRelease, executeReleasePlan } from './release-plan';
import { captureChangesInReleaseBranch } from './repo';
import { createReleaseBranch, captureChangesInReleaseBranch } from './repo';
import {
generateReleaseSpecificationTemplateForMonorepo,
waitForUserToEditReleaseSpecification,
validateReleaseSpecification,
} from './release-specification';
import { updateChangedPackagesChangelog } from './package';

/**
* For a monorepo, the process works like this:
Expand Down Expand Up @@ -64,6 +65,15 @@ export async function followMonorepoWorkflow({
stdout: Pick<WriteStream, 'write'>;
stderr: Pick<WriteStream, 'write'>;
}) {
const { version: newReleaseVersion, firstRun } = await createReleaseBranch({
project,
releaseType,
});

if (firstRun) {
await updateChangedPackagesChangelog({ project, stderr });
}

const releaseSpecificationPath = path.join(
tempDirectoryPath,
'RELEASE_SPEC.yml',
Expand Down Expand Up @@ -115,11 +125,11 @@ export async function followMonorepoWorkflow({
const releasePlan = await planRelease({
project,
releaseSpecification,
releaseType,
newReleaseVersion,
});
await executeReleasePlan(project, releasePlan, stderr);
await removeFile(releaseSpecificationPath);
await captureChangesInReleaseBranch(project.directoryPath, {
releaseVersion: releasePlan.newVersion,
releaseVersion: newReleaseVersion,
});
}
60 changes: 60 additions & 0 deletions src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,66 @@ export async function readMonorepoWorkspacePackage({
};
}

/**
* Updates the changelog file of the given package using
* `@metamask/auto-changelog`. Assumes that the changelog file is located at the
* package root directory and named "CHANGELOG.md".
*
* @param args - The arguments.
* @param args.project - The project.
* @param args.stderr - A stream that can be used to write to standard error.
* @returns The result of writing to the changelog.
*/
export async function updateChangedPackagesChangelog({
project: { repositoryUrl, workspacePackages },
stderr,
}: {
project: Pick<
Project,
'directoryPath' | 'repositoryUrl' | 'workspacePackages'
>;
stderr: Pick<WriteStream, 'write'>;
}): Promise<void> {
await Promise.all(
Object.values(workspacePackages)
.filter(
({ hasChangesSinceLatestRelease }) => hasChangesSinceLatestRelease,
)
.map(async (pkg) => {
let changelogContent;

try {
changelogContent = await readFile(pkg.changelogPath);
} catch (error) {
if (isErrorWithCode(error) && error.code === 'ENOENT') {
stderr.write(
`${pkg.validatedManifest.name} does not seem to have a changelog. Skipping.\n`,
);
return;
}

throw error;
}

const newChangelogContent = await updateChangelog({
changelogContent,
isReleaseCandidate: false,
projectRootDirectory: pkg.directoryPath,
repoUrl: repositoryUrl,
tagPrefixes: [`${pkg.validatedManifest.name}@`, 'v'],
});

if (newChangelogContent) {
await writeFile(pkg.changelogPath, newChangelogContent);
} else {
stderr.write(
`Changelog for ${pkg.validatedManifest.name} was not updated as there were no updates to make.`,
);
}
}),
);
}

/**
* Updates the changelog file of the given package using
* `@metamask/auto-changelog`. Assumes that the changelog file is located at the
Expand Down
17 changes: 4 additions & 13 deletions src/release-plan.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { WriteStream } from 'fs';
import { SemVer } from 'semver';
import { ReleaseType } from './initial-parameters';
import { debug } from './misc-utils';
import { Package, updatePackage } from './package';
import { Project } from './project';
Expand Down Expand Up @@ -48,33 +47,25 @@ export type PackageReleasePlan = {

/**
* Uses the release specification to calculate the final versions of all of the
* packages that we want to update, as well as a new release name.
* packages that we want to update.
*
* @param args - The arguments.
* @param args.project - Information about the whole project (e.g., names of
* packages and where they can found).
* @param args.releaseSpecification - A parsed version of the release spec
* entered by the user.
* @param args.releaseType - The type of release ("ordinary" or "backport"),
* which affects how the version is bumped.
* @param args.newReleaseVersion - The new release version.
* @returns A promise for information about the new release.
*/
export async function planRelease({
project,
releaseSpecification,
releaseType,
newReleaseVersion,
}: {
project: Project;
releaseSpecification: ReleaseSpecification;
releaseType: ReleaseType;
newReleaseVersion: string;
}): Promise<ReleasePlan> {
const newReleaseVersion =
releaseType === 'backport'
? `${project.releaseVersion.ordinaryNumber}.${
project.releaseVersion.backportNumber + 1
}.0`
: `${project.releaseVersion.ordinaryNumber + 1}.0.0`;

const rootReleasePlan: PackageReleasePlan = {
package: project.rootPackage,
newVersion: newReleaseVersion,
Expand Down
86 changes: 76 additions & 10 deletions src/repo.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import path from 'path';
import {
debug,
runCommand,
getStdoutFromCommand,
getLinesFromCommand,
} from './misc-utils';
import { ReleaseType } from './initial-parameters';
import { Project } from './project';

const CHANGED_FILE_PATHS_BY_TAG_NAME: Record<string, string[]> = {};

Expand Down Expand Up @@ -170,13 +173,8 @@ export async function getRepositoryHttpsUrl(
}

/**
* This function does three things:
*
* 1. Stages all of the changes which have been made to the repo thus far and
* This function stages all of the changes which have been made to the repo thus far and
* creates a new Git commit which carries the name of the new release.
* 2. Creates a new branch pointed to that commit (which also carries the name
* of the new release).
* 3. Switches to that branch.
*
* @param repositoryDirectoryPath - The path to the repository directory.
* @param args - The arguments.
Expand All @@ -186,17 +184,85 @@ export async function captureChangesInReleaseBranch(
repositoryDirectoryPath: string,
{ releaseVersion }: { releaseVersion: string },
) {
await getStdoutFromGitCommandWithin(repositoryDirectoryPath, 'checkout', [
'-b',
`release/${releaseVersion}`,
]);
await getStdoutFromGitCommandWithin(repositoryDirectoryPath, 'add', ['-A']);
await getStdoutFromGitCommandWithin(repositoryDirectoryPath, 'commit', [
'-m',
`Release ${releaseVersion}`,
]);
}

/**
* This function does create the release branch.
*
* @param args - The arguments.
* @param args.project - Information about the whole project (e.g., names of
* packages and where they can found).
* @param args.releaseType - The type of release ("ordinary" or "backport"),
* which affects how the version is bumped.
* @returns A promise for the newReleaseVersion.
*/
export async function createReleaseBranch({
project,
releaseType,
}: {
project: Project;
releaseType: ReleaseType;
}): Promise<{
version: string;
firstRun: boolean;
}> {
const newReleaseVersion =
releaseType === 'backport'
? `${project.releaseVersion.ordinaryNumber}.${
project.releaseVersion.backportNumber + 1
}.0`
: `${project.releaseVersion.ordinaryNumber + 1}.0.0`;

const releaseBranchName = `release/${newReleaseVersion}`;

const currentBranchName = await getStdoutFromGitCommandWithin(
project.directoryPath,
'rev-parse',
['--abbrev-ref', 'HEAD'],
);

if (currentBranchName === releaseBranchName) {
debug(`Already on ${releaseBranchName} branch.`);
return {
version: newReleaseVersion,
firstRun: false,
};
}

if (
await getStdoutFromGitCommandWithin(project.directoryPath, 'branch', [
'--list',
releaseBranchName,
])
) {
debug(
`Current release branch already exists. Checking out the existing branch.`,
);
await getStdoutFromGitCommandWithin(project.directoryPath, 'checkout', [
releaseBranchName,
]);
return {
version: newReleaseVersion,
firstRun: false,
};
}

await getStdoutFromGitCommandWithin(project.directoryPath, 'checkout', [
'-b',
releaseBranchName,
]);

return {
version: newReleaseVersion,
firstRun: true,
};
}

/**
* Retrieves the names of the tags in the given repo, sorted by ascending
* semantic version order. As this fetches tags from the remote first, you are
Expand Down

0 comments on commit ddbb6da

Please sign in to comment.