Skip to content

Commit

Permalink
Add support for nested workspaces
Browse files Browse the repository at this point in the history
  • Loading branch information
Mrtenz committed Jul 14, 2023
1 parent a391815 commit 0868c8f
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 22 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 4 additions & 2 deletions src/functional.test.ts
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/misc-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions src/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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(
Expand Down
44 changes: 28 additions & 16 deletions src/project.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
*
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions tests/functional/helpers/local-monorepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
3 changes: 1 addition & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 0868c8f

Please sign in to comment.