From 0868c8fc15813b2fedd0de77ab842f453afe07cc Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 14 Jul 2023 19:37:50 +0200 Subject: [PATCH] Add support for nested workspaces --- package.json | 1 - src/functional.test.ts | 6 ++- src/misc-utils.ts | 2 +- src/project.test.ts | 14 +++++++ src/project.ts | 44 ++++++++++++++-------- tests/functional/helpers/local-monorepo.ts | 6 +++ yarn.lock | 3 +- 7 files changed, 54 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 4173eaf..e82358d 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "@metamask/utils": "^5.0.2", "debug": "^4.3.4", "execa": "^5.1.1", - "glob": "^10.2.2", "pony-cause": "^2.1.9", "semver": "^7.5.0", "which": "^3.0.0", diff --git a/src/functional.test.ts b/src/functional.test.ts index 86f9b49..59c692a 100644 --- a/src/functional.test.ts +++ b/src/functional.test.ts @@ -1,6 +1,8 @@ import { withMonorepoProjectEnvironment } from '../tests/functional/helpers/with'; import { buildChangelog } from '../tests/functional/helpers/utils'; +jest.setTimeout(60_000); + describe('create-release-branch (functional)', () => { describe('against a monorepo with independent versions', () => { it('bumps the ordinary part of the root package and updates the versions of the specified packages according to the release spec', async () => { @@ -654,16 +656,16 @@ Error: Your release spec could not be processed due to the following issues: * The following packages, which have changed since their latest release, are missing. - - @scope/d - @scope/b + - @scope/d Consider including them in the release spec so that any packages that rely on them won't break in production. If you are ABSOLUTELY SURE that this won't occur, however, and want to postpone the release of a package, then list it with a directive of "intentionally-skip". For example: packages: - "@scope/d": intentionally-skip "@scope/b": intentionally-skip + "@scope/d": intentionally-skip The release spec file has been retained for you to edit again and make the necessary fixes. Once you've done this, re-run this tool. diff --git a/src/misc-utils.ts b/src/misc-utils.ts index 14e5367..a63aee0 100644 --- a/src/misc-utils.ts +++ b/src/misc-utils.ts @@ -155,7 +155,7 @@ export async function getStdoutFromCommand( } /** - * Runs a Git command, splitting up the immediate output into lines. + * Run a command, splitting up the immediate output into lines. * * @param command - The command to execute. * @param args - The positional arguments to the command. diff --git a/src/project.test.ts b/src/project.test.ts index 205066f..6ceb8ae 100644 --- a/src/project.test.ts +++ b/src/project.test.ts @@ -7,9 +7,11 @@ import { buildMockPackage, createNoopWriteStream } from '../tests/unit/helpers'; import { readProject } from './project'; import * as packageModule from './package'; import * as repoModule from './repo'; +import * as miscUtilsModule from './misc-utils'; jest.mock('./package'); jest.mock('./repo'); +jest.mock('./misc-utils'); describe('project', () => { describe('readProject', () => { @@ -57,6 +59,18 @@ describe('project', () => { projectTagNames, }) .mockResolvedValue(rootPackage); + when(jest.spyOn(miscUtilsModule, 'getLinesFromCommand')) + .calledWith( + 'yarn', + ['workspaces', 'list', '--no-private', '--json'], + { + cwd: projectDirectoryPath, + }, + ) + .mockResolvedValue([ + '{"location":"packages/a","name":"a","workspaceDependencies":[],"mismatchedWorkspaceDependencies":[],"workspaceDependents":[]}', + '{"location":"packages/subpackages/b","name":"b","workspaceDependencies":[],"mismatchedWorkspaceDependencies":[],"workspaceDependents":[]}', + ]); when(jest.spyOn(packageModule, 'readMonorepoWorkspacePackage')) .calledWith({ packageDirectoryPath: path.join( diff --git a/src/project.ts b/src/project.ts index fbc6e0b..3ec6033 100644 --- a/src/project.ts +++ b/src/project.ts @@ -1,13 +1,13 @@ -import { glob } from 'glob'; +import { resolve } from 'path'; import { WriteStreamLike } from './fs'; import { Package, readMonorepoRootPackage, readMonorepoWorkspacePackage, } from './package'; -import { PackageManifestFieldNames } from './package-manifest'; import { getRepositoryHttpsUrl, getTagNames } from './repo'; import { SemVer } from './semver'; +import { getLinesFromCommand } from './misc-utils'; /** * The release version of the root package of a monorepo extracted from its @@ -27,6 +27,11 @@ type ReleaseVersion = { backportNumber: number; }; +export type YarnWorkspace = { + location: string; + name: string; +}; + /** * Represents the entire codebase on which this tool is operating. * @@ -63,6 +68,24 @@ function examineReleaseVersion(packageVersion: SemVer): ReleaseVersion { }; } +/** + * List the workspaces in a monorepo using Yarn. + * + * @param projectDirectoryPath - The path to the project. + * @returns An array of objects that represent the workspaces. + */ +export async function listWorkspaces(projectDirectoryPath: string) { + const stdout = await getLinesFromCommand( + 'yarn', + ['workspaces', 'list', '--no-private', '--json'], + { + cwd: projectDirectoryPath, + }, + ); + + return stdout.map((line) => JSON.parse(line) as YarnWorkspace); +} + /** * Collects information about a monorepo — its root package as well as any * packages within workspaces specified via the root `package.json`. @@ -90,24 +113,13 @@ export async function readProject( rootPackage.validatedManifest.version, ); - const workspaceDirectories = ( - await Promise.all( - rootPackage.validatedManifest[PackageManifestFieldNames.Workspaces].map( - async (workspacePattern) => { - return await glob(workspacePattern, { - cwd: projectDirectoryPath, - absolute: true, - }); - }, - ), - ) - ).flat(); + const workspaceDirectories = await listWorkspaces(projectDirectoryPath); const workspacePackages = ( await Promise.all( - workspaceDirectories.map(async (directory) => { + workspaceDirectories.map(async ({ location }) => { return await readMonorepoWorkspacePackage({ - packageDirectoryPath: directory, + packageDirectoryPath: resolve(projectDirectoryPath, location), rootPackageName: rootPackage.validatedManifest.name, rootPackageVersion: rootPackage.validatedManifest.version, projectDirectoryPath, diff --git a/tests/functional/helpers/local-monorepo.ts b/tests/functional/helpers/local-monorepo.ts index 4fdab70..54caef2 100644 --- a/tests/functional/helpers/local-monorepo.ts +++ b/tests/functional/helpers/local-monorepo.ts @@ -50,6 +50,12 @@ export default class LocalMonorepo< this.#workspaces = workspaces; } + async create() { + await super.create(); + await this.runCommand('yarn', ['set', 'version', 'stable']); + await this.runCommand('yarn'); + } + /** * Reads a file within a workspace package within the project. * diff --git a/yarn.lock b/yarn.lock index e615e4a..5d5f90a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1002,7 +1002,6 @@ __metadata: eslint-plugin-node: ^11.1.0 eslint-plugin-prettier: ^4.2.1 execa: ^5.1.1 - glob: ^10.2.2 jest: ^29.5.0 jest-it-up: ^2.0.2 jest-when: ^3.5.2 @@ -3160,7 +3159,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2": +"glob@npm:^10.0.0": version: 10.2.2 resolution: "glob@npm:10.2.2" dependencies: