Skip to content

Commit 9ebd422

Browse files
authored
feat(manifest): node workspace package dependency updates (googleapis#844)
Introduces the concept of a "plugin" that can run after release-please has generated changes but before the PR is created. In order to support plugin releaser-post-processing (before submitting the PR) we want to provide the whole contents of the changed file to the plugin.
1 parent 6f24661 commit 9ebd422

19 files changed

Lines changed: 3460 additions & 214 deletions

__snapshots__/manifest.js

Lines changed: 187 additions & 134 deletions
Large diffs are not rendered by default.

__snapshots__/node-workspace.js

Lines changed: 1244 additions & 0 deletions
Large diffs are not rendered by default.

docs/manifest-releaser.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ documented in comments)
100100
// - only applicable at top-level config.
101101
"bootstrap-sha": "6fc119838885b0cb831e78ddd23ac01cb819e585",
102102

103+
// see Plugins section below
104+
// absence defaults to [] (i.e. no plugins)
105+
"plugins": ["node-workspace"],
103106

104107
// optional top-level defaults that can be overriden per package:
105108

@@ -249,3 +252,72 @@ with a combined version of the library, i.e.,
249252
This functionality can be achieved by using the special `"."` path.
250253
`"."` indicates a release should be created when any changes are made to the
251254
codebase.
255+
256+
## Plugins
257+
258+
Plugins can be added to perform extra release processing that cannot be achieved
259+
by an individual releaser because that releaser only has the context of a single
260+
package on which to operate. A plugin operates in the context of all the
261+
packages in the monorepo after release-please has run individual releasers on
262+
each package but before the final PR is created or updated.
263+
264+
### Plugin usage
265+
266+
To use a plugin in your manifest based release-please setup, simply add it to
267+
the array of the `"plugins"` key in your release-please-config.json. (Note: the
268+
plugin must already be implemented, see below)
269+
270+
### Plugin implementation
271+
272+
A `ManifestPlugin` instance has these resources avilable:
273+
- `this.gh`: a `GitHub` instance for any API operations it might want to perform
274+
- `this.config`: a `Config` object representing all the packages configured for the monorepo
275+
276+
It must implement a `run` method which receives two arguments:
277+
- the latest versions of all packages (ultimately be written to the manifest)
278+
- an array of the mapping of package-to-currently-proposed-changes
279+
and makes any modifications, additions, or deletions to either argument (in
280+
addition to any other out-of-band side effect) and returns both (potentially
281+
modified) arguments in a tuple.
282+
283+
For example, a very basic plugin that simply logs the number of packages
284+
currently appearing in the release written as `src/plugins/num-packages.ts`:
285+
286+
```typescript
287+
import {CheckpointType} from '../util/checkpoint';
288+
289+
export default class LogNumberPkgsReleased extends ManifestPlugin {
290+
291+
async run(
292+
newManifestVersions: VersionsMap,
293+
pkgsWithPRData: ManifestPackageWithPRData[]
294+
): Promise<[VersionsMap, ManifestPackageWithPRData[]]> {
295+
this.log(
296+
`Number of packages to release: ${pkgsWithPRData.length}`,
297+
CheckpointType.Success
298+
);
299+
return [newManifestVersions, pkgsWithPRData];
300+
}
301+
}
302+
```
303+
304+
The `num-packages` plugin is not very interesting. Also, if it is not last in
305+
the `"plugins"` configuration array, it might not be accurate (a subsequent
306+
plugin could add or remove entries to/from `pkgsWithPRData`)
307+
308+
However, one place a plugin has particular value is in a monorepo where local
309+
packages depend on the latest version of each other (e.g. yarn/npm workspaces
310+
for Node, or cargo workspaces for Rust).
311+
312+
313+
### node-workspace
314+
315+
The `node-workspace` plugin builds a graph of local node packages configured
316+
in release-please-config.json and the dependency relationships between them.
317+
It looks at what packages were updated by release-please and updates their
318+
reference in other packages' dependencies lists. Even when a particular package
319+
was not updated by release-please, if a dependency did have an update, it will
320+
be patch bump the package, create a changelog entry, and add it to the list of
321+
PR changes. Under the hood this plugin adapts specific dependency graph building
322+
and updating functionality from the popular
323+
[lerna](https://github.com/lerna/lerna) tool.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,14 @@
6161
"dependencies": {
6262
"@conventional-commits/parser": "^0.4.1",
6363
"@iarna/toml": "^2.2.5",
64+
"@lerna/collect-updates": "^4.0.0",
65+
"@lerna/package": "^3.16.0",
66+
"@lerna/package-graph": "^3.18.5",
67+
"@lerna/run-topologically": "^3.18.5",
6468
"@octokit/graphql": "^4.3.1",
6569
"@octokit/request": "^5.3.4",
6670
"@octokit/rest": "^18.0.4",
71+
"@types/npm-package-arg": "^6.1.0",
6772
"chalk": "^4.0.0",
6873
"code-suggester": "^1.4.0",
6974
"conventional-changelog-conventionalcommits": "^4.4.0",

src/github.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export interface GitHubPR {
112112
body: string;
113113
updates: Update[];
114114
labels: string[];
115+
changes?: Changes;
115116
}
116117

117118
export interface MergedGitHubPR {
@@ -1184,8 +1185,10 @@ export class GitHub {
11841185
return undefined;
11851186
}
11861187

1187-
// Actually update the files for the release:
1188-
const changes = await this.getChangeSet(options.updates, defaultBranch);
1188+
// Update the files for the release if not already supplied
1189+
const changes =
1190+
options.changes ??
1191+
(await this.getChangeSet(options.updates, defaultBranch));
11891192
const prNumber = await createPullRequest(
11901193
this.octokit,
11911194
changes,
@@ -1225,7 +1228,7 @@ export class GitHub {
12251228
}
12261229
}
12271230

1228-
private async getChangeSet(
1231+
async getChangeSet(
12291232
updates: Update[],
12301233
defaultBranch: string
12311234
): Promise<Changes> {

src/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {ReleaseType} from './releasers';
1717
import {ReleasePR} from './release-pr';
1818
import {ChangelogSection} from './conventional-commits';
1919
import {Checkpoint} from './util/checkpoint';
20+
import {Changes} from 'code-suggester';
2021

2122
export {ReleaseCandidate, ReleasePR} from './release-pr';
2223

@@ -80,6 +81,35 @@ export interface ManifestFactoryOptions
8081
extends GitHubFactoryOptions,
8182
ManifestOptions {}
8283

84+
// allow list of releaser options used to build ReleasePR subclass instances
85+
// and GitHubRelease instances for each package. Rejected items:
86+
// 1. `monorepoTags` and `pullRequestTitlePattern` will never apply
87+
// 2. `lastPackageVersion` and `versionFile` could be supported if/when ruby
88+
// support in the manifest is made available. However, ideally they'd be
89+
// factored out of the ruby ReleasePR prior to adding support here.
90+
// 3. currently unsupported but possible future support:
91+
// - `snapshot`
92+
// - `label`
93+
export type ManifestPackage = Pick<
94+
ReleasePROptions & GitHubReleaseOptions,
95+
| 'draft'
96+
| 'packageName'
97+
| 'bumpMinorPreMajor'
98+
| 'bumpPatchForMinorPreMajor'
99+
| 'releaseAs'
100+
| 'changelogSections'
101+
| 'changelogPath'
102+
> & {
103+
// these items are not optional in the manifest context.
104+
path: string;
105+
releaseType: ReleaseType;
106+
};
107+
108+
export interface ManifestPackageWithPRData {
109+
config: ManifestPackage;
110+
prData: {version: string; changes: Changes};
111+
}
112+
83113
// ReleasePR Constructor options
84114
export interface ReleasePRConstructorOptions
85115
extends ReleasePROptions,

0 commit comments

Comments
 (0)