diff --git a/index.js b/index.js index 6341ba3..5a546da 100644 --- a/index.js +++ b/index.js @@ -50,6 +50,7 @@ module.exports = async (argv) => { "update-default-branch", "github-pages", "retarget-pull-requests", + "retarget-draft-releases", "branch-protection", "delete-old-branch", ]; diff --git a/transforms/retarget-draft-releases.js b/transforms/retarget-draft-releases.js new file mode 100644 index 0000000..6fb584e --- /dev/null +++ b/transforms/retarget-draft-releases.js @@ -0,0 +1,58 @@ +module.exports = async function ({ + owner, + repo, + old, + target, + dryRun, + log, + octokit, +}) { + log = log.extend("transforms:retarget-draft-releases"); + + log("Fetching all draft releases"); + // List all PRs + let releases = await octokit.paginate( + "GET /repos/:owner/:repo/releases", + { + owner, + repo, + per_page: 100, + }, + (response) => response.data + ); + log(`Found [${releases.length}] releases`); + + if (releases.length == 0) { + log(`No releases found. Exiting`); + return; + } + + // Filter down to draft releases + releases = releases.filter((r) => r.draft); + if (releases.length === 0) { + log(`No draft releases found. Exiting`); + return; + } + + // Update the target branch for all open PRs + for (let release of releases) { + if (release.target_commitish != old) { + log(`Skipping [${release.name}] as it didn't target [${old}]`); + continue; + } + + log( + `Updating release [${release.name}] in [${repo}] from [${release.target_commitish}] to [${target}]` + ); + + if (!dryRun) { + await octokit.repos.updateRelease({ + owner, + repo, + release_id: release.id, + target_commitish: target, + tag_name: release.tag_name, + }); + } + } +}; diff --git a/transforms/retarget-draft-releases.test.js b/transforms/retarget-draft-releases.test.js new file mode 100644 index 0000000..725926a --- /dev/null +++ b/transforms/retarget-draft-releases.test.js @@ -0,0 +1,142 @@ +const nock = require("nock"); +nock.disableNetConnect(); + +const log = require("../src/mock-debug"); + +const retarget = require("./retarget-draft-releases"); +const octokit = require("../src/octokit")(); + +afterEach(() => { + if (!nock.isDone()) { + throw new Error("Not all nock interceptors were used"); + } + log.cleanAll(); + nock.cleanAll(); +}); + +describe("#retarget-pull-requests", () => { + it("exits if there are no draft releases", async () => { + nock("https://api.github.com/") + .get("/repos/demo/repo/releases?per_page=100") + .reply(200, []); + + await retarget({ + owner: "demo", + repo: "repo", + target: "main", + old: "master", + octokit, + log, + }); + + expect(log.logger).toBeCalledWith("Found [0] releases"); + expect(log.logger).toBeCalledWith("No releases found. Exiting"); + }); + + it("exits if there are no draft releases", async () => { + nock("https://api.github.com/") + .get("/repos/demo/repo/releases?per_page=100") + .reply(200, [{ draft: false }]); + + await retarget({ + owner: "demo", + repo: "repo", + target: "main", + old: "master", + octokit, + log, + }); + + expect(log.logger).toBeCalledWith("No draft releases found. Exiting"); + }); + + it("skips releases with a different target", async () => { + nock("https://api.github.com/") + .get("/repos/demo/repo/releases?per_page=100") + .reply(200, [ + { + name: "OtherTag", + target_commitish: "other-branch", + draft: true, + }, + ]); + + await retarget({ + owner: "demo", + repo: "repo", + target: "main", + old: "master", + octokit, + log, + }); + + expect(log.logger).toBeCalledWith("Found [1] releases"); + expect(log.logger).toBeCalledWith( + "Skipping [OtherTag] as it didn't target [master]" + ); + }); + + it("retargets valid pull requests", async () => { + nock("https://api.github.com/") + .get("/repos/demo/repo/releases?per_page=100") + .reply(200, [ + { + id: 1234, + name: "OtherTag", + target_commitish: "master", + tag_name: "v1", + draft: true, + }, + ]); + + nock("https://api.github.com/") + .patch("/repos/demo/repo/releases/1234", { + target_commitish: "main", + tag_name: "v1", + }) + .reply(200); + + await retarget({ + owner: "demo", + repo: "repo", + target: "main", + old: "master", + octokit, + log, + }); + + expect(log.logger).toBeCalledWith("Found [1] releases"); + expect(log.logger).toBeCalledWith( + "Updating release [OtherTag] in [repo] from [master] to [main]" + ); + }); + + it("respects dryRun mode", async () => { + nock("https://api.github.com/") + .get("/repos/demo/repo/releases?per_page=100") + .reply(200, [ + { + id: 1234, + name: "OtherTag", + target_commitish: "master", + tag_name: "v1", + draft: true, + }, + ]); + + await retarget({ + owner: "demo", + repo: "repo", + target: "main", + old: "master", + octokit, + log, + dryRun: true, + }); + + expect(log.logger).toBeCalledWith("Found [1] releases"); + expect(log.logger).toBeCalledWith( + "Updating release [OtherTag] in [repo] from [master] to [main]" + ); + }); +});