Skip to content

Commit

Permalink
List package versions in releases for monorepos with independent vers…
Browse files Browse the repository at this point in the history
…ioning (#79)

Currently, the notes for an independently-versioned monorepo release
lists all of the packages that are included in that release, but does
not list the versions of those packages that are being published. This
makes reviewing past releases difficult as we have to cross-reference
package tags with monorepo tags to determine those versions.

This commit fixes that. For instance, instead of seeing the following
for a GitHub release:

> # 59.0.0
>
> ## @metamask/base-controller
>
> (release notes for base-controller)
>
> ## @metamask/controller-utils
>
> (release notes for controller-utils)

we should now see:

> # 59.0.0
>
> ## @metamask/base-controller 4.0.0
>
> (release notes for base-controller)
>
> ## @metamask/controller-utils 5.0.0
>
> (release notes for controller-utils)
  • Loading branch information
mcmire authored Jun 26, 2023
1 parent b5b116c commit 59765eb
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 55 deletions.
18 changes: 9 additions & 9 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11034,31 +11034,31 @@ async function getReleaseNotes() {
console.log('Project does not appear to have any workspaces. Applying polyrepo workflow.');
releaseNotes = await getPackageReleaseNotes(releaseVersion, repoUrl, workspaceRoot);
}
releaseNotes = releaseNotes.trim();
if (!releaseNotes) {
const trimmedReleaseNotes = releaseNotes.trim();
if (!trimmedReleaseNotes) {
throw new Error('The computed release notes are empty.');
}
(0,core.exportVariable)('RELEASE_NOTES', releaseNotes.concat('\n\n'));
(0,core.exportVariable)('RELEASE_NOTES', trimmedReleaseNotes);
}
async function getReleaseNotesForMonorepoWithIndependentVersions(repoUrl) {
let releaseNotes = '';
const releaseNotesParts = [];
for (const [packageName, { path, version }] of Object.entries(getReleasePackages())) {
releaseNotes = releaseNotes.concat(`## ${packageName}\n\n`, await getPackageReleaseNotes(version, repoUrl, path), '\n\n');
releaseNotesParts.push(`## ${packageName} ${version}`, await getPackageReleaseNotes(version, repoUrl, path));
}
return releaseNotes;
return releaseNotesParts.join('\n\n');
}
async function getReleaseNotesForMonorepoWithFixedVersions(releaseVersion, repoUrl, workspaceRoot, rootManifest) {
const workspaceLocations = await (0,dist.getWorkspaceLocations)(rootManifest.workspaces, workspaceRoot);
let releaseNotes = '';
const releaseNotesParts = [];
for (const workspaceLocation of workspaceLocations) {
const completeWorkspacePath = external_path_default().join(workspaceRoot, workspaceLocation);
const rawPackageManifest = await (0,dist.getPackageManifest)(completeWorkspacePath);
const { name: packageName, version: packageVersion } = (0,dist.validatePolyrepoPackageManifest)(rawPackageManifest, completeWorkspacePath);
if (packageVersion === releaseVersion) {
releaseNotes = releaseNotes.concat(`## ${packageName}\n\n`, await getPackageReleaseNotes(releaseVersion, repoUrl, completeWorkspacePath), '\n\n');
releaseNotesParts.push(`## ${packageName}`, await getPackageReleaseNotes(releaseVersion, repoUrl, completeWorkspacePath));
}
}
return releaseNotes;
return releaseNotesParts.join('\n\n');
}
/**
* Gets the combined release notes for all packages in the monorepo that are
Expand Down
88 changes: 57 additions & 31 deletions src/getReleaseNotes.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from 'assert';
import fs from 'fs';
import * as actionsCore from '@actions/core';
import * as autoChangelog from '@metamask/auto-changelog';
Expand Down Expand Up @@ -41,20 +42,6 @@ jest.mock('./utils', () => {
};
});

const parseChangelogMockImplementation = ({
changelogContent,
}: {
changelogContent: string;
}) => {
return {
// getStringifiedRelease returns a string whose first line is a markdown
// e.g. "## 1.0.0\n". This is stripped by getReleaseNotes.
getStringifiedRelease(version: string) {
return `## Header\nrelease ${version} for ${changelogContent.slice(-1)}`;
},
};
};

describe('getReleasePackages', () => {
let parseEnvVariablesMock: jest.SpyInstance;

Expand Down Expand Up @@ -169,7 +156,7 @@ describe('getReleaseNotes', () => {
expect(exportActionVariableMock).toHaveBeenCalledTimes(1);
expect(exportActionVariableMock).toHaveBeenCalledWith(
'RELEASE_NOTES',
`${mockReleaseBody}\n\n`,
mockReleaseBody,
);
});

Expand All @@ -178,7 +165,7 @@ describe('getReleaseNotes', () => {
const mockRepoUrl = 'https:/Org/Name';
const mockVersion = '1.0.0';
const mockWorkspaces = ['a', 'b', 'c'];
const mockChangelog = 'a changelog';
const mockChangelog = 'changelog';
const mockVersionStrategy = 'fixed';

parseEnvVariablesMock.mockImplementationOnce(() => {
Expand Down Expand Up @@ -216,14 +203,22 @@ describe('getReleaseNotes', () => {
});

// Return a different changelog for each package/workspace
readFileMock.mockImplementation(
async (path: string) =>
`${mockChangelog} for ${path.charAt(
path.indexOf('/CHANGELOG.md') - 1,
)}`,
);
readFileMock.mockImplementation(async (path: string) => {
const match = path.match(/^foo\/packages\/([^/]+)\/CHANGELOG.md$/u);
assert(match, 'Failed to extract package name');
const packageName = match[1];
return `${mockChangelog} for ${packageName}`;
});

parseChangelogMock.mockImplementation(parseChangelogMockImplementation);
parseChangelogMock.mockImplementation(({ changelogContent }) => {
return {
// getStringifiedRelease returns a string whose first line is a markdown
// e.g. "## 1.0.0\n". This is stripped by getReleaseNotes.
getStringifiedRelease(_version: string) {
return `## Header\n${changelogContent}`;
},
};
});

await releaseNotesUtils.getReleaseNotes();

Expand All @@ -250,19 +245,27 @@ describe('getReleaseNotes', () => {
);
expect(parseChangelogMock).toHaveBeenCalledTimes(2);
expect(parseChangelogMock).toHaveBeenNthCalledWith(1, {
changelogContent: 'a changelog for a',
changelogContent: 'changelog for a',
repoUrl: mockRepoUrl,
});
expect(parseChangelogMock).toHaveBeenNthCalledWith(2, {
changelogContent: 'a changelog for c',
changelogContent: 'changelog for c',
repoUrl: mockRepoUrl,
});

// Finally, the Action output, as an environment variable
expect(exportActionVariableMock).toHaveBeenCalledTimes(1);
expect(exportActionVariableMock).toHaveBeenCalledWith(
'RELEASE_NOTES',
`## a\n\nrelease 1.0.0 for a\n\n## c\n\nrelease 1.0.0 for c\n\n`,
`
## a
changelog for a
## c
changelog for c
`.trim(),
);
});

Expand Down Expand Up @@ -317,12 +320,27 @@ describe('getReleaseNotes', () => {

// Return a different changelog for each package/workspace
readFileMock.mockImplementation(async (path: string) => {
return `${mockChangelog} for ${path.charAt(
path.indexOf('/CHANGELOG.md') - 1,
)}`;
const match = path.match(/^packages\/([^/]+)\/CHANGELOG.md$/u);
assert(match, 'Failed to extract package name');
const packageName = match[1];
return `${mockChangelog} for ${packageName}`;
});

parseChangelogMock.mockImplementation(parseChangelogMockImplementation);
parseChangelogMock.mockImplementation(({ changelogContent }) => {
const match = changelogContent.match(
new RegExp(`^${mockChangelog} for (.+)$`, 'u'),
);
assert(match, 'Failed to extract package name');
const packageName = match[1];

return {
// getStringifiedRelease returns a string whose first line is a markdown
// e.g. "## 1.0.0\n". This is stripped by getReleaseNotes.
getStringifiedRelease(version: string) {
return `## Header\nrelease ${version} for ${packageName}`;
},
};
});

await releaseNotesUtils.getReleaseNotes();

Expand All @@ -348,7 +366,15 @@ describe('getReleaseNotes', () => {
expect(exportActionVariableMock).toHaveBeenCalledTimes(1);
expect(exportActionVariableMock).toHaveBeenCalledWith(
'RELEASE_NOTES',
`## @metamask/base-controller\n\nrelease 0.3.0 for r\n\n## @metamask/controller-utils\n\nrelease 0.7.1 for s\n\n`,
`
## @metamask/base-controller 0.3.0
release 0.3.0 for base-controller
## @metamask/controller-utils 0.7.1
release 0.7.1 for controller-utils
`.trim(),
);
});

Expand Down
32 changes: 17 additions & 15 deletions src/getReleaseNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,41 +67,43 @@ export async function getReleaseNotes() {
);
}

releaseNotes = releaseNotes.trim();
if (!releaseNotes) {
const trimmedReleaseNotes = releaseNotes.trim();
if (!trimmedReleaseNotes) {
throw new Error('The computed release notes are empty.');
}
exportActionVariable('RELEASE_NOTES', releaseNotes.concat('\n\n'));
exportActionVariable('RELEASE_NOTES', trimmedReleaseNotes);
}

async function getReleaseNotesForMonorepoWithIndependentVersions(
repoUrl: string,
) {
let releaseNotes = '';
): Promise<string> {
const releaseNotesParts = [];

for (const [packageName, { path, version }] of Object.entries(
getReleasePackages(),
)) {
releaseNotes = releaseNotes.concat(
`## ${packageName}\n\n`,
releaseNotesParts.push(
`## ${packageName} ${version}`,
await getPackageReleaseNotes(version, repoUrl, path),
'\n\n',
);
}
return releaseNotes;

return releaseNotesParts.join('\n\n');
}

async function getReleaseNotesForMonorepoWithFixedVersions(
releaseVersion: string,
repoUrl: string,
workspaceRoot: string,
rootManifest: MonorepoPackageManifest,
) {
): Promise<string> {
const workspaceLocations = await getWorkspaceLocations(
rootManifest.workspaces,
workspaceRoot,
);

let releaseNotes = '';
const releaseNotesParts = [];

for (const workspaceLocation of workspaceLocations) {
const completeWorkspacePath = pathUtils.join(
workspaceRoot,
Expand All @@ -116,18 +118,18 @@ async function getReleaseNotesForMonorepoWithFixedVersions(
);

if (packageVersion === releaseVersion) {
releaseNotes = releaseNotes.concat(
`## ${packageName}\n\n`,
releaseNotesParts.push(
`## ${packageName}`,
await getPackageReleaseNotes(
releaseVersion,
repoUrl,
completeWorkspacePath,
),
'\n\n',
);
}
}
return releaseNotes;

return releaseNotesParts.join('\n\n');
}

/**
Expand Down

0 comments on commit 59765eb

Please sign in to comment.