diff --git a/src/package-utils.test.ts b/src/package-utils.test.ts index 93295f8..f8105bf 100644 --- a/src/package-utils.test.ts +++ b/src/package-utils.test.ts @@ -1,3 +1,4 @@ +import { glob } from 'glob'; import * as fileUtils from './file-utils'; import { getPackageManifest, @@ -9,14 +10,7 @@ import { validatePolyrepoPackageManifest, } from './package-utils'; -jest.mock('util', () => { - return { - promisify: jest.fn().mockImplementation( - // This is effectively the mock of the promisified glob main export - () => async (val: unknown, _options: Record) => val, - ), - }; -}); +jest.mock('glob'); describe('getPackageManifest', () => { let readJsonFileMock: jest.SpyInstance; @@ -164,11 +158,49 @@ describe('validatePolyrepoPackageManifest', () => { }); describe('getWorkspaceLocations', () => { + const mockGlob = (value: string[]) => ( + _pattern: string, + _options: unknown, + callback: (error: null, data: string[]) => void, + ) => callback(null, value); + it('does the thing', async () => { const workspaces = ['foo/bar', 'fizz/buzz']; const rootDir = 'dir'; - expect(await getWorkspaceLocations(workspaces, rootDir)).toStrictEqual([ - ...workspaces, - ]); + + (glob as jest.MockedFunction) + .mockImplementationOnce(mockGlob(['foo/bar'])) + .mockImplementationOnce(mockGlob(['fizz/buzz'])); + + expect(await getWorkspaceLocations(workspaces, rootDir)).toStrictEqual( + workspaces, + ); + }); + + it('does the thing, but recursively', async () => { + (glob as jest.MockedFunction) + .mockImplementationOnce(mockGlob(['foo/bar'])) + .mockImplementationOnce(mockGlob(['baz'])) + .mockImplementationOnce(mockGlob(['qux'])); + + jest + .spyOn(fileUtils, 'readJsonObjectFile') + .mockImplementationOnce(async () => ({ + [ManifestFieldNames.Version]: '1.0.0', + [ManifestFieldNames.Private]: true, + [ManifestFieldNames.Workspaces]: ['baz'], + })) + .mockImplementationOnce(async () => ({ + [ManifestFieldNames.Version]: '1.0.0', + [ManifestFieldNames.Private]: true, + [ManifestFieldNames.Workspaces]: ['qux'], + })) + .mockImplementation(async () => ({ + [ManifestFieldNames.Version]: '1.0.0', + })); + + expect( + await getWorkspaceLocations(['foo/bar'], 'dir', true), + ).toStrictEqual(['foo/bar', 'foo/bar/baz', 'foo/bar/baz/qux']); }); }); diff --git a/src/package-utils.ts b/src/package-utils.ts index 15c8abf..71656fb 100644 --- a/src/package-utils.ts +++ b/src/package-utils.ts @@ -263,14 +263,63 @@ function getManifestErrorMessagePrefix( * * @param workspaces - The list of workspace patterns given in the root manifest. * @param rootDir - The monorepo root directory. + * @param recursive - Whether to search recursively. * @returns The location of each workspace directory relative to the root directory */ export async function getWorkspaceLocations( workspaces: string[], rootDir: string, + recursive = false, + prefix = '', ): Promise { - const resolvedWorkspaces = await Promise.all( - workspaces.map((pattern) => glob(pattern, { cwd: rootDir })), + const resolvedWorkspaces = await workspaces.reduce>( + async (promise, pattern) => { + const array = await promise; + const matches = (await glob(pattern, { cwd: rootDir })).map((match) => + pathUtils.join(prefix, match), + ); + + return [...array, ...matches]; + }, + Promise.resolve([]), ); - return resolvedWorkspaces.flat(); + + if (recursive) { + // This reads all the package JSON files in each workspace, checks if they are a monorepo, and + // recursively calls `getWorkspaceLocations` if they are. + const resolvedSubWorkspaces = await resolvedWorkspaces.reduce< + Promise + >(async (promise, workspacePath) => { + const array = await promise; + + const rawManifest = await getPackageManifest(workspacePath); + if (ManifestFieldNames.Workspaces in rawManifest) { + const manifest = validatePackageManifestVersion( + rawManifest, + workspacePath, + ); + + const monorepoManifest = validateMonorepoPackageManifest( + manifest, + workspacePath, + ); + + return [ + ...array, + ...(await getWorkspaceLocations( + monorepoManifest[ManifestFieldNames.Workspaces], + workspacePath, + recursive, + workspacePath, + )), + ]; + } + + return array; + }, Promise.resolve(resolvedWorkspaces)); + + return resolvedSubWorkspaces; + } + + return resolvedWorkspaces; }