From 657a7ca0c9be5ea2d8b4e15e69818290b2b29d6b Mon Sep 17 00:00:00 2001 From: Daniel Azuma Date: Mon, 18 Apr 2022 22:54:06 +0000 Subject: [PATCH] feat: Support sequential-calls manifest field that disables concurrency when creating multiple pull requests or releases --- docs/cli.md | 3 +- docs/manifest-releaser.md | 8 +++++ src/manifest.ts | 68 ++++++++++++++++++++++++++++----------- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 839b1262e..aa4b970b9 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -56,7 +56,6 @@ Extra options: | `--draft-pull-request` | `boolean` | If set, create pull requests as drafts | | `--label` | `string` | Comma-separated list of labels to apply to the release pull requests. Defaults to `autorelease: pending` | | `--release-label` | `string` | Comma-separated list of labels to apply to the pull request after the release has been tagged. Defaults to `autorelease: tagged` | -| `--skip-labeling` | `boolean` | If set, labels will not be applied to pull requests | | `--changelog-path` | `string` | Override the path to the managed CHANGELOG. Defaults to `CHANGELOG.md` | | `--changelog-type` | [`ChangelogType`](/docs/customizing.md#changelog-types) | Strategy for building the changelog contents. Defaults to `default` | | `--changelog-sections` | `string` | Comma-separated list of commit scopes to show in changelog headings | @@ -83,6 +82,7 @@ Extra options: | ------ | ---- | ----------- | | `--config-file` | string | Override the path to the release-please config file. Defaults to `release-please-config.json` | | `--manifest-file` | string | Override the path to the release-please manifest file. Defaults to `.release-please-manifest.json` | +| `--skip-labeling` | `boolean` | If set, labels will not be applied to pull requests | ### Without a manifest config @@ -109,6 +109,7 @@ need to specify your release options: | `--signoff` | string | Add [`Signed-off-by`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff) line at the end of the commit log message using the user and email provided. (format "Name \") | | `--extra-files` | `string[]` | Extra file paths for the release strategy to consider | | `--version-file` | `string` | Ruby only. Path to the `version.rb` file | +| `--skip-labeling` | `boolean` | If set, labels will not be applied to pull requests | ## Creating a release on GitHub diff --git a/docs/manifest-releaser.md b/docs/manifest-releaser.md index 0756a2caa..c74d92c5e 100644 --- a/docs/manifest-releaser.md +++ b/docs/manifest-releaser.md @@ -225,6 +225,14 @@ documented in comments) // value, but it will increase the number of API calls used. "commit-search-depth": 500, + // when creating multiple pull requests or releases, issue GitHub API requests + // sequentially rather than concurrently, waiting for the previous request to + // complete before issuing the next one. + // This option may reduce failures due to throttling on repositories releasing + // large numbers of packages at once. + // absence defaults to false, causing calls to be issued concurrently. + "sequential-calls": false, + // per package configuration: at least one entry required. // the key is the relative path from the repo root to the folder that contains // all the files for that package. diff --git a/src/manifest.ts b/src/manifest.ts index 448c521cb..f494026a9 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -134,9 +134,10 @@ export interface ManifestOptions { signoff?: string; manifestPath?: string; labels?: string[]; - skipLabeling?: boolean; releaseLabels?: string[]; snapshotLabels?: string[]; + skipLabeling?: boolean; + sequentialCalls?: boolean; draft?: boolean; prerelease?: boolean; draftPullRequest?: boolean; @@ -178,6 +179,7 @@ export interface ManifestConfig extends ReleaserConfigJson { 'group-pull-request-title-pattern'?: string; 'release-search-depth'?: number; 'commit-search-depth'?: number; + 'sequential-calls'?: boolean; } // path => version export type ReleasedVersions = Record; @@ -216,6 +218,7 @@ export class Manifest { private signoffUser?: string; private labels: string[]; private skipLabeling?: boolean; + private sequentialCalls?: boolean; private releaseLabels: string[]; private snapshotLabels: string[]; private plugins: PluginType[]; @@ -280,6 +283,7 @@ export class Manifest { manifestOptions?.releaseLabels || DEFAULT_RELEASE_LABELS; this.labels = manifestOptions?.labels || DEFAULT_LABELS; this.skipLabeling = manifestOptions?.skipLabeling || false; + this.sequentialCalls = manifestOptions?.sequentialCalls || false; this.snapshotLabels = manifestOptions?.snapshotLabels || DEFAULT_SNAPSHOT_LABELS; this.bootstrapSha = manifestOptions?.bootstrapSha; @@ -720,7 +724,7 @@ export class Manifest { /** * Opens/updates all candidate release pull requests for this repository. * - * @returns {number[]} Pull request numbers of release pull requests + * @returns {PullRequest[]} Pull request numbers of release pull requests */ async createPullRequests(): Promise<(PullRequest | undefined)[]> { const candidatePullRequests = await this.buildPullRequests(); @@ -742,19 +746,32 @@ export class Manifest { const openPullRequests = await this.findOpenReleasePullRequests(); const snoozedPullRequests = await this.findSnoozedReleasePullRequests(); - const promises: Promise[] = []; - for (const pullRequest of candidatePullRequests) { - promises.push( - this.createOrUpdatePullRequest( + if (this.sequentialCalls) { + const pullRequests: PullRequest[] = []; + for (const pullRequest of candidatePullRequests) { + const resultPullRequest = await this.createOrUpdatePullRequest( pullRequest, openPullRequests, snoozedPullRequests - ) - ); + ); + if (resultPullRequest) pullRequests.push(resultPullRequest); + } + return pullRequests; + } else { + const promises: Promise[] = []; + for (const pullRequest of candidatePullRequests) { + promises.push( + this.createOrUpdatePullRequest( + pullRequest, + openPullRequests, + snoozedPullRequests + ) + ); + } + const pullNumbers = await Promise.all(promises); + // reject any pull numbers that were not created or updated + return pullNumbers.filter(number => !!number); } - const pullNumbers = await Promise.all(promises); - // reject any pull numbers that were not created or updated - return pullNumbers.filter(number => !!number); } private async findOpenReleasePullRequests(): Promise { @@ -968,17 +985,29 @@ export class Manifest { } } - const promises: Promise[] = []; - for (const pullNumber in releasesByPullRequest) { - promises.push( - this.createReleasesForPullRequest( + if (this.sequentialCalls) { + const resultReleases: CreatedRelease[] = []; + for (const pullNumber in releasesByPullRequest) { + const releases = await this.createReleasesForPullRequest( releasesByPullRequest[pullNumber], pullRequestsByNumber[pullNumber] - ) - ); + ); + resultReleases.concat(releases); + } + return resultReleases; + } else { + const promises: Promise[] = []; + for (const pullNumber in releasesByPullRequest) { + promises.push( + this.createReleasesForPullRequest( + releasesByPullRequest[pullNumber], + pullRequestsByNumber[pullNumber] + ) + ); + } + const releases = await Promise.all(promises); + return releases.reduce((collection, r) => collection.concat(r), []); } - const releases = await Promise.all(promises); - return releases.reduce((collection, r) => collection.concat(r), []); } private async createReleasesForPullRequest( @@ -1158,6 +1187,7 @@ async function parseConfig( configSnapshotLabel === undefined ? undefined : [configSnapshotLabel], releaseSearchDepth: config['release-search-depth'], commitSearchDepth: config['commit-search-depth'], + sequentialCalls: config['sequential-calls'], }; return {config: repositoryConfig, options: manifestOptions}; }