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
56 changes: 42 additions & 14 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12489,30 +12489,54 @@ var auto_changelog_dist = __nccwpck_require__(9272);
const MANIFEST_FILE_NAME = 'package.json';
const CHANGELOG_FILE_NAME = 'CHANGELOG.md';
/**
* Finds the package manifest for each workspace, and collects
* Recursively finds the package manifest for each workspace, and collects
* metadata for each package.
*
* @param workspaces - The list of workspace patterns given in the root manifest.
* @param rootDir - The monorepo root directory.
* @param parentDir - The parent directory of the current package.
* @returns The metadata for all packages in the monorepo.
*/
async function getMetadataForAllPackages(workspaces, rootDir = WORKSPACE_ROOT) {
async function getMetadataForAllPackages(workspaces, rootDir = WORKSPACE_ROOT, parentDir = '') {
const workspaceLocations = await (0,dist.getWorkspaceLocations)(workspaces, rootDir);
const result = {};
await Promise.all(workspaceLocations.map(async (workspaceDirectory) => {
return workspaceLocations.reduce(async (promise, workspaceDirectory) => {
const result = await promise;
const fullWorkspacePath = external_path_default().join(rootDir, workspaceDirectory);
if ((await external_fs_.promises.lstat(fullWorkspacePath)).isDirectory()) {
const rawManifest = await (0,dist.getPackageManifest)(fullWorkspacePath);
// If the package is a sub-workspace, resolve all packages in the sub-workspace and add them
// to the result.
if (dist.ManifestFieldNames.Workspaces in rawManifest) {
const rootManifest = (0,dist.validatePackageManifestVersion)(rawManifest, workspaceDirectory);
const manifest = (0,dist.validateMonorepoPackageManifest)(rootManifest, workspaceDirectory);
const name = manifest[dist.ManifestFieldNames.Name];
if (!name) {
throw new Error(`Expected sub-workspace in "${workspaceDirectory}" to have a name.`);
}
return {
...result,
...(await getMetadataForAllPackages(manifest.workspaces, workspaceDirectory, workspaceDirectory)),
[name]: {
dirName: external_path_default().basename(workspaceDirectory),
manifest,
name,
dirPath: external_path_default().join(parentDir, workspaceDirectory),
},
};
}
const manifest = (0,dist.validatePolyrepoPackageManifest)(rawManifest, workspaceDirectory);
result[manifest.name] = {
dirName: external_path_default().basename(workspaceDirectory),
manifest,
name: manifest.name,
dirPath: workspaceDirectory,
return {
...result,
[manifest.name]: {
dirName: external_path_default().basename(workspaceDirectory),
manifest,
name: manifest.name,
dirPath: external_path_default().join(parentDir, workspaceDirectory),
},
};
}
}));
return result;
return result;
}, Promise.resolve({}));
}
/**
* @param allPackages - The metadata of all packages in the monorepo.
Expand Down Expand Up @@ -12596,8 +12620,12 @@ async function updatePackageChangelog(packageMetadata, updateSpecification, root
changelogContent = await external_fs_.promises.readFile(changelogPath, 'utf-8');
}
catch (error) {
console.error(`Failed to read changelog in "${projectRootDirectory}".`);
throw error;
// If the error is not a file not found error, throw it
if (error.code !== 'ENOENT') {
console.error(`Failed to read changelog in "${projectRootDirectory}".`);
throw error;
}
return console.warn(`Failed to read changelog in "${projectRootDirectory}".`);
}
const newChangelogContent = await (0,auto_changelog_dist.updateChangelog)({
changelogContent,
Expand All @@ -12610,7 +12638,7 @@ async function updatePackageChangelog(packageMetadata, updateSpecification, root
const packageName = packageMetadata.manifest.name;
throw new Error(`"updateChangelog" returned an empty value for package ${packageName ? `"${packageName}"` : `at "${packagePath}"`}.`);
}
await external_fs_.promises.writeFile(changelogPath, newChangelogContent);
return await external_fs_.promises.writeFile(changelogPath, newChangelogContent);
}
/**
* Updates the given manifest per the update specification as follows:
Expand Down
137 changes: 120 additions & 17 deletions src/package-operations.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import fs from 'fs';
import cloneDeep from 'lodash.clonedeep';
import * as actionUtils from '@metamask/action-utils/dist/file-utils';
import * as actionUtils from '@metamask/action-utils';
import {
ManifestDependencyFieldNames,
ManifestFieldNames,
} from '@metamask/action-utils';
import * as autoChangelog from '@metamask/auto-changelog';
import glob from 'glob';
import * as gitOps from './git-operations';
import {
getMetadataForAllPackages,
Expand All @@ -23,20 +24,7 @@ jest.mock('fs', () => ({
},
}));

jest.mock('glob', () => {
return (
_pattern: string,
_options: Record<string, unknown>,
callback: (error: Error | null, results: string[]) => unknown,
) => {
callback(null, [
'packages/dir1',
'packages/dir2',
'packages/dir3',
'packages/someFile',
]);
};
});
jest.mock('glob');

jest.mock('@metamask/action-utils/dist/file-utils', () => {
const actualModule = jest.requireActual(
Expand Down Expand Up @@ -119,19 +107,98 @@ describe('package-operations', () => {
? { isDirectory: () => false }
: { isDirectory: () => true };
}) as any);
});

it('does not throw', async () => {
(glob as jest.MockedFunction<any>).mockImplementation(
(
_pattern: string,
_options: unknown,
callback: (error: null, data: string[]) => void,
) =>
callback(null, [
'packages/dir1',
'packages/dir2',
'packages/dir3',
'packages/someFile',
]),
);

jest
.spyOn(actionUtils, 'readJsonObjectFile')
.mockImplementation(getMockReadJsonFile());
});

it('does not throw', async () => {
expect(await getMetadataForAllPackages(['packages/*'])).toStrictEqual({
[names[0]]: getMockPackageMetadata(0),
[names[1]]: getMockPackageMetadata(1),
[names[2]]: getMockPackageMetadata(2),
});
});

it('resolves recursive workspaces', async () => {
(glob as jest.MockedFunction<any>)
.mockImplementationOnce(
(
_pattern: string,
_options: unknown,
callback: (error: null, data: string[]) => void,
) => callback(null, ['packages/dir1']),
)
.mockImplementationOnce(
(
_pattern: string,
_options: unknown,
callback: (error: null, data: string[]) => void,
) => callback(null, ['packages/dir2']),
);

jest
.spyOn(actionUtils, 'readJsonObjectFile')
.mockImplementationOnce(async () => ({
...getMockManifest(names[0], version),
private: true,
workspaces: ['packages/*'],
}))
.mockImplementationOnce(async () => getMockManifest(names[1], version));

expect(await getMetadataForAllPackages(['packages/*'])).toStrictEqual({
[names[0]]: {
...getMockPackageMetadata(0),
manifest: {
...getMockManifest(names[0], version),
private: true,
workspaces: ['packages/*'],
},
},
[names[1]]: {
...getMockPackageMetadata(1),
dirPath: 'packages/dir1/packages/dir2',
},
});
});

it('throws if a sub-workspace does not have a name', async () => {
(glob as jest.MockedFunction<any>).mockImplementationOnce(
(
_pattern: string,
_options: unknown,
callback: (error: null, data: string[]) => void,
) => callback(null, ['packages/dir1']),
);

jest
.spyOn(actionUtils, 'readJsonObjectFile')
.mockImplementationOnce(async () => ({
...getMockManifest(names[0], version),
private: true,
workspaces: ['packages/*'],
name: undefined,
}));

await expect(getMetadataForAllPackages(['packages/*'])).rejects.toThrow(
'Expected sub-workspace in "packages/dir1" to have a name.',
);
});
});

describe('getPackagesToUpdate', () => {
Expand Down Expand Up @@ -313,6 +380,42 @@ describe('package-operations', () => {
);
});

it('does not throw if the file cannot be found', async () => {
const originalVersion = '1.0.0';
const newVersion = '1.0.1';
const dir = mockDirs[0];
const name = packageNames[0];
const manifest = getMockManifest(name, originalVersion);

readFileMock.mockImplementationOnce(async () => {
const error = new Error('readError');
(error as any).code = 'ENOENT';

throw error;
});

const packageMetadata = getMockPackageMetadata(dir, manifest);
const updateSpecification = {
newVersion,
packagesToUpdate: new Set(packageNames),
repositoryUrl: 'https://fake',
shouldUpdateChangelog: true,
synchronizeVersions: false,
};

const consoleWarnSpy = jest
.spyOn(console, 'warn')
.mockImplementationOnce(() => undefined);

await updatePackage(packageMetadata, updateSpecification);

expect(updateChangelogMock).not.toHaveBeenCalled();
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringMatching(/^Failed to read changelog/u),
);
});

it('throws if updated changelog is empty', async () => {
const originalVersion = '1.0.0';
const newVersion = '1.0.1';
Expand Down
Loading