Skip to content

Commit

Permalink
feat(manifest): node workspace package dependency updates (#844)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
joeldodge79 committed Apr 9, 2021
1 parent 6f24661 commit 9ebd422
Show file tree
Hide file tree
Showing 19 changed files with 3,460 additions and 214 deletions.
321 changes: 187 additions & 134 deletions __snapshots__/manifest.js

Large diffs are not rendered by default.

1,244 changes: 1,244 additions & 0 deletions __snapshots__/node-workspace.js

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions docs/manifest-releaser.md
Expand Up @@ -100,6 +100,9 @@ documented in comments)
// - only applicable at top-level config.
"bootstrap-sha": "6fc119838885b0cb831e78ddd23ac01cb819e585",

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

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

Expand Down Expand Up @@ -249,3 +252,72 @@ with a combined version of the library, i.e.,
This functionality can be achieved by using the special `"."` path.
`"."` indicates a release should be created when any changes are made to the
codebase.

## Plugins

Plugins can be added to perform extra release processing that cannot be achieved
by an individual releaser because that releaser only has the context of a single
package on which to operate. A plugin operates in the context of all the
packages in the monorepo after release-please has run individual releasers on
each package but before the final PR is created or updated.

### Plugin usage

To use a plugin in your manifest based release-please setup, simply add it to
the array of the `"plugins"` key in your release-please-config.json. (Note: the
plugin must already be implemented, see below)

### Plugin implementation

A `ManifestPlugin` instance has these resources avilable:
- `this.gh`: a `GitHub` instance for any API operations it might want to perform
- `this.config`: a `Config` object representing all the packages configured for the monorepo

It must implement a `run` method which receives two arguments:
- the latest versions of all packages (ultimately be written to the manifest)
- an array of the mapping of package-to-currently-proposed-changes
and makes any modifications, additions, or deletions to either argument (in
addition to any other out-of-band side effect) and returns both (potentially
modified) arguments in a tuple.

For example, a very basic plugin that simply logs the number of packages
currently appearing in the release written as `src/plugins/num-packages.ts`:

```typescript
import {CheckpointType} from '../util/checkpoint';

export default class LogNumberPkgsReleased extends ManifestPlugin {

async run(
newManifestVersions: VersionsMap,
pkgsWithPRData: ManifestPackageWithPRData[]
): Promise<[VersionsMap, ManifestPackageWithPRData[]]> {
this.log(
`Number of packages to release: ${pkgsWithPRData.length}`,
CheckpointType.Success
);
return [newManifestVersions, pkgsWithPRData];
}
}
```

The `num-packages` plugin is not very interesting. Also, if it is not last in
the `"plugins"` configuration array, it might not be accurate (a subsequent
plugin could add or remove entries to/from `pkgsWithPRData`)

However, one place a plugin has particular value is in a monorepo where local
packages depend on the latest version of each other (e.g. yarn/npm workspaces
for Node, or cargo workspaces for Rust).


### node-workspace

The `node-workspace` plugin builds a graph of local node packages configured
in release-please-config.json and the dependency relationships between them.
It looks at what packages were updated by release-please and updates their
reference in other packages' dependencies lists. Even when a particular package
was not updated by release-please, if a dependency did have an update, it will
be patch bump the package, create a changelog entry, and add it to the list of
PR changes. Under the hood this plugin adapts specific dependency graph building
and updating functionality from the popular
[lerna](https://github.com/lerna/lerna) tool.
5 changes: 5 additions & 0 deletions package.json
Expand Up @@ -61,9 +61,14 @@
"dependencies": {
"@conventional-commits/parser": "^0.4.1",
"@iarna/toml": "^2.2.5",
"@lerna/collect-updates": "^4.0.0",
"@lerna/package": "^3.16.0",
"@lerna/package-graph": "^3.18.5",
"@lerna/run-topologically": "^3.18.5",
"@octokit/graphql": "^4.3.1",
"@octokit/request": "^5.3.4",
"@octokit/rest": "^18.0.4",
"@types/npm-package-arg": "^6.1.0",
"chalk": "^4.0.0",
"code-suggester": "^1.4.0",
"conventional-changelog-conventionalcommits": "^4.4.0",
Expand Down
9 changes: 6 additions & 3 deletions src/github.ts
Expand Up @@ -112,6 +112,7 @@ export interface GitHubPR {
body: string;
updates: Update[];
labels: string[];
changes?: Changes;
}

export interface MergedGitHubPR {
Expand Down Expand Up @@ -1184,8 +1185,10 @@ export class GitHub {
return undefined;
}

// Actually update the files for the release:
const changes = await this.getChangeSet(options.updates, defaultBranch);
// Update the files for the release if not already supplied
const changes =
options.changes ??
(await this.getChangeSet(options.updates, defaultBranch));
const prNumber = await createPullRequest(
this.octokit,
changes,
Expand Down Expand Up @@ -1225,7 +1228,7 @@ export class GitHub {
}
}

private async getChangeSet(
async getChangeSet(
updates: Update[],
defaultBranch: string
): Promise<Changes> {
Expand Down
30 changes: 30 additions & 0 deletions src/index.ts
Expand Up @@ -17,6 +17,7 @@ import {ReleaseType} from './releasers';
import {ReleasePR} from './release-pr';
import {ChangelogSection} from './conventional-commits';
import {Checkpoint} from './util/checkpoint';
import {Changes} from 'code-suggester';

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

Expand Down Expand Up @@ -80,6 +81,35 @@ export interface ManifestFactoryOptions
extends GitHubFactoryOptions,
ManifestOptions {}

// allow list of releaser options used to build ReleasePR subclass instances
// and GitHubRelease instances for each package. Rejected items:
// 1. `monorepoTags` and `pullRequestTitlePattern` will never apply
// 2. `lastPackageVersion` and `versionFile` could be supported if/when ruby
// support in the manifest is made available. However, ideally they'd be
// factored out of the ruby ReleasePR prior to adding support here.
// 3. currently unsupported but possible future support:
// - `snapshot`
// - `label`
export type ManifestPackage = Pick<
ReleasePROptions & GitHubReleaseOptions,
| 'draft'
| 'packageName'
| 'bumpMinorPreMajor'
| 'bumpPatchForMinorPreMajor'
| 'releaseAs'
| 'changelogSections'
| 'changelogPath'
> & {
// these items are not optional in the manifest context.
path: string;
releaseType: ReleaseType;
};

export interface ManifestPackageWithPRData {
config: ManifestPackage;
prData: {version: string; changes: Changes};
}

// ReleasePR Constructor options
export interface ReleasePRConstructorOptions
extends ReleasePROptions,
Expand Down

0 comments on commit 9ebd422

Please sign in to comment.