From 6c9b81e30b80c38d73d6c42291dc7bfccd130513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 1 Apr 2026 08:57:56 +0200 Subject: [PATCH] refactor --- .changeset/brown-cycles-attack.md | 2 +- README.md | 2 +- action.yml | 5 +- src/__snapshots__/run.test.ts.snap | 119 +++++++++++++++++++++++++++++ src/index.ts | 7 +- src/run.test.ts | 106 +++++++++++++++++++++++++ src/run.ts | 48 ++++++++++-- 7 files changed, 276 insertions(+), 13 deletions(-) diff --git a/.changeset/brown-cycles-attack.md b/.changeset/brown-cycles-attack.md index b834150..5692aae 100644 --- a/.changeset/brown-cycles-attack.md +++ b/.changeset/brown-cycles-attack.md @@ -2,4 +2,4 @@ "@changesets/action": minor --- -Support creating the pull request in draft mode using a new `createPrAsDraft` input. +Support draft version PR modes with a new `prDraft` input. Use `create` to create new version PRs as drafts, or `always` to also convert existing version PRs back to draft when updating them. diff --git a/README.md b/README.md index 8ad20ec..cf9c6b0 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This action for [Changesets](https://github.com/changesets/changesets) creates a - createGithubReleases - A boolean value to indicate whether to create Github releases after `publish` or not. Default to `true` - commitMode - Specifies the commit mode. Use `"git-cli"` to push changes using the Git CLI, or `"github-api"` to push changes via the GitHub API. When using `"github-api"`, all commits and tags are GPG-signed and attributed to the user or app who owns the `GITHUB_TOKEN`. Default to `git-cli`. - cwd - Changes node's `process.cwd()` if the project is not located on the root. Default to `process.cwd()` -- createPrAsDraft - A boolean value to indicate whether to create the PR as draft or not. Default to `false` +- prDraft - Controls draft PR behavior. Use `create` to create new version PRs as draft, or `always` to also convert existing version PRs back to draft when updating them. By default, version PRs are not forced into draft mode. ### Outputs diff --git a/action.yml b/action.yml index 8343436..9f7d386 100644 --- a/action.yml +++ b/action.yml @@ -43,10 +43,9 @@ inputs: branch: description: Sets the branch in which the action will run. Default to `github.ref_name` if not provided required: false - createPrAsDraft: - description: "A boolean value to indicate whether to create the PR as draft or not" + prDraft: + description: 'Controls draft PR behavior. Use "create" to create new version PRs as draft, or "always" to also convert existing version PRs back to draft when updating them.' required: false - default: false outputs: published: description: A boolean value to indicate whether a publishing is happened or not diff --git a/src/__snapshots__/run.test.ts.snap b/src/__snapshots__/run.test.ts.snap index aa9b5d9..956ed69 100644 --- a/src/__snapshots__/run.test.ts.snap +++ b/src/__snapshots__/run.test.ts.snap @@ -1,5 +1,28 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`version > creates a draft PR when prDraft is "create" 1`] = ` +[ + { + "base": "some-branch", + "body": "This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to some-branch, this PR will be updated. + + +# Releases +## simple-project-pkg-a@1.1.0 + +### Minor Changes + +- Awesome feature +", + "draft": true, + "head": "changeset-release/some-branch", + "owner": "changesets", + "repo": "action", + "title": "Version Packages", + }, +] +`; + exports[`version > creates simple PR 1`] = ` [ { @@ -25,6 +48,7 @@ exports[`version > creates simple PR 1`] = ` - Awesome feature ", + "draft": false, "head": "changeset-release/some-branch", "owner": "changesets", "repo": "action", @@ -43,6 +67,7 @@ exports[`version > does not include any release information if a message with si # Releases > All release information have been omitted from this message, as the content exceeds the size limit.", + "draft": false, "head": "changeset-release/some-branch", "owner": "changesets", "repo": "action", @@ -65,6 +90,7 @@ exports[`version > does not include changelog entries if full message exceeds si ## simple-project-pkg-a@1.1.0 ", + "draft": false, "head": "changeset-release/some-branch", "owner": "changesets", "repo": "action", @@ -87,6 +113,7 @@ exports[`version > doesn't include ignored package that got a dependency update - Awesome feature ", + "draft": false, "head": "changeset-release/some-branch", "owner": "changesets", "repo": "action", @@ -109,6 +136,7 @@ exports[`version > only includes bumped packages in the PR body 1`] = ` - Awesome feature ", + "draft": false, "head": "changeset-release/some-branch", "owner": "changesets", "repo": "action", @@ -116,3 +144,94 @@ exports[`version > only includes bumped packages in the PR body 1`] = ` }, ] `; + +exports[`version > updates an existing PR via GraphQL and converts it to draft when prDraft is "always" 1`] = ` +[ + " + mutation UpdatePullRequest( + $pullRequestId: ID! + $title: String! + $body: String! + ) { + + convertPullRequestToDraft( + input: { + pullRequestId: $pullRequestId + } + ) { + pullRequest { + id + } + } + + updatePullRequest( + input: { + pullRequestId: $pullRequestId + title: $title + body: $body + state: OPEN + } + ) { + pullRequest { + id + } + } + } + ", + { + "body": "This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to some-branch, this PR will be updated. + + +# Releases +## simple-project-pkg-a@1.1.0 + +### Minor Changes + +- Awesome feature +", + "pullRequestId": "PR_kwDOA", + "title": "Version Packages", + }, +] +`; + +exports[`version > updates an existing PR via GraphQL without converting it to draft when prDraft is "create" 1`] = ` +[ + " + mutation UpdatePullRequest( + $pullRequestId: ID! + $title: String! + $body: String! + ) { + + + updatePullRequest( + input: { + pullRequestId: $pullRequestId + title: $title + body: $body + state: OPEN + } + ) { + pullRequest { + id + } + } + } + ", + { + "body": "This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to some-branch, this PR will be updated. + + +# Releases +## simple-project-pkg-a@1.1.0 + +### Minor Changes + +- Awesome feature +", + "pullRequestId": "PR_kwDOA", + "title": "Version Packages", + }, +] +`; diff --git a/src/index.ts b/src/index.ts index ad227f9..32d3334 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,10 +24,15 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined; const octokit = setupOctokit(githubToken); const commitMode = getOptionalInput("commitMode") ?? "git-cli"; + const prDraft = getOptionalInput("prDraft"); if (commitMode !== "git-cli" && commitMode !== "github-api") { core.setFailed(`Invalid commit mode: ${commitMode}`); return; } + if (prDraft !== undefined && prDraft !== "always" && prDraft !== "create") { + core.setFailed(`Invalid prDraft: ${prDraft}`); + return; + } const git = new Git({ octokit: commitMode === "github-api" ? octokit : undefined, cwd, @@ -147,7 +152,7 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined; prTitle: getOptionalInput("title"), commitMessage: getOptionalInput("commit"), hasPublishScript, - createPrAsDraft: core.getBooleanInput("createPrAsDraft"), + prDraft, branch: getOptionalInput("branch"), }); diff --git a/src/run.test.ts b/src/run.test.ts index 90c9fc0..34fba34 100644 --- a/src/run.test.ts +++ b/src/run.test.ts @@ -19,6 +19,7 @@ vi.mock("@actions/github", () => ({ }, getOctokit: () => ({ rest: mockedGithubMethods, + graphql: mockedGraphql, }), })); vi.mock("./git.ts"); @@ -33,6 +34,7 @@ let mockedGithubMethods = { createRelease: vi.fn(), }, }; +let mockedGraphql = vi.fn(); let f = fixturez(import.meta.dirname); @@ -90,6 +92,42 @@ describe("version", () => { expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot(); }); + it('creates a draft PR when prDraft is "create"', async () => { + let cwd = f.copy("simple-project"); + await linkNodeModules(cwd); + + mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ data: [] })); + + mockedGithubMethods.pulls.create.mockImplementationOnce(() => ({ + data: { number: 123 }, + })); + + await writeChangesets( + [ + { + releases: [ + { + name: "simple-project-pkg-a", + type: "minor", + }, + ], + summary: "Awesome feature", + }, + ], + cwd + ); + + await runVersion({ + octokit: setupOctokit("@@GITHUB_TOKEN"), + githubToken: "@@GITHUB_TOKEN", + git: new Git({ cwd }), + cwd, + prDraft: "create", + }); + + expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot(); + }); + it("only includes bumped packages in the PR body", async () => { let cwd = f.copy("simple-project"); await linkNodeModules(cwd); @@ -277,4 +315,72 @@ fluminis divesque vulnere aquis parce lapsis rabie si visa fulmineis. /All release information have been omitted from this message, as the content exceeds the size limit/ ); }); + + it('updates an existing PR via GraphQL without converting it to draft when prDraft is "create"', async () => { + let cwd = f.copy("simple-project"); + await linkNodeModules(cwd); + + mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ + data: [{ number: 123, node_id: "PR_kwDOA" }], + })); + + await writeChangesets( + [ + { + releases: [ + { + name: "simple-project-pkg-a", + type: "minor", + }, + ], + summary: "Awesome feature", + }, + ], + cwd + ); + + await runVersion({ + octokit: setupOctokit("@@GITHUB_TOKEN"), + githubToken: "@@GITHUB_TOKEN", + git: new Git({ cwd }), + cwd, + prDraft: "create", + }); + + expect(mockedGraphql.mock.calls[0]).toMatchSnapshot(); + }); + + it('updates an existing PR via GraphQL and converts it to draft when prDraft is "always"', async () => { + let cwd = f.copy("simple-project"); + await linkNodeModules(cwd); + + mockedGithubMethods.pulls.list.mockImplementationOnce(() => ({ + data: [{ number: 123, node_id: "PR_kwDOA" }], + })); + + await writeChangesets( + [ + { + releases: [ + { + name: "simple-project-pkg-a", + type: "minor", + }, + ], + summary: "Awesome feature", + }, + ], + cwd + ); + + await runVersion({ + octokit: setupOctokit("@@GITHUB_TOKEN"), + githubToken: "@@GITHUB_TOKEN", + git: new Git({ cwd }), + cwd, + prDraft: "always", + }); + + expect(mockedGraphql.mock.calls[0]).toMatchSnapshot(); + }); }); diff --git a/src/run.ts b/src/run.ts index 6d886d1..e779f58 100644 --- a/src/run.ts +++ b/src/run.ts @@ -259,7 +259,7 @@ type VersionOptions = { commitMessage?: string; hasPublishScript?: boolean; prBodyMaxCharacters?: number; - createPrAsDraft?: boolean; + prDraft?: "always" | "create"; branch?: string; }; @@ -278,7 +278,7 @@ export async function runVersion({ hasPublishScript = false, prBodyMaxCharacters = MAX_CHARACTERS_PER_MESSAGE, branch = github.context.ref.replace("refs/heads/", ""), - createPrAsDraft = false, + prDraft, }: VersionOptions): Promise { let versionBranch = `changeset-release/${branch}`; @@ -378,7 +378,7 @@ export async function runVersion({ head: versionBranch, title: finalPrTitle, body: prBody, - draft: createPrAsDraft, + draft: prDraft !== undefined, ...github.context.repo, }); @@ -389,12 +389,46 @@ export async function runVersion({ const [pullRequest] = existingPullRequests.data; core.info(`updating found pull request #${pullRequest.number}`); - await octokit.rest.pulls.update({ - pull_number: pullRequest.number, + const convertPullRequestToDraftMutation = + prDraft === "always" + ? ` + convertPullRequestToDraft( + input: { + pullRequestId: $pullRequestId + } + ) { + pullRequest { + id + } + }` + : ""; + const updatePullRequestMutation = ` + mutation UpdatePullRequest( + $pullRequestId: ID! + $title: String! + $body: String! + ) { + ${convertPullRequestToDraftMutation} + + updatePullRequest( + input: { + pullRequestId: $pullRequestId + title: $title + body: $body + state: OPEN + } + ) { + pullRequest { + id + } + } + } + `; + + await octokit.graphql(updatePullRequestMutation, { + pullRequestId: pullRequest.node_id, title: finalPrTitle, body: prBody, - ...github.context.repo, - state: "open", }); return {