diff --git a/.changeset/lazy-glasses-stop.md b/.changeset/lazy-glasses-stop.md deleted file mode 100644 index 0198d1f2..00000000 --- a/.changeset/lazy-glasses-stop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@changesets/action": patch ---- - -Fix custom version and publish command argument parsing diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e1713db..de2ebf4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,9 @@ name: CI on: + push: + branches: + - main pull_request: # merge queue is required so all commits on target branches trigger this workflow # despite lack of the push event trigger here diff --git a/.gitignore b/.gitignore index c4f6b826..29bb28cc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules *.log dist/ +.pnpm-store diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b2f2bb6..7b3345f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # @changesets/action +## 1.8.4 + +### Patch Changes + +- Use REST when gitea/forgejo to update PR + +## 1.8.3 + +### Patch Changes + +- [#629](https://github.com/changesets/action/pull/629) [`e0c90aa`](https://github.com/changesets/action/commit/e0c90aa7fbd0cc26931a679c5abe9bbc0deb0b50) Thanks [@bluwy](https://github.com/bluwy)! - Fix custom version and publish command argument parsing + +- [`76380c7`](https://github.com/changesets/action/commit/76380c7a8b581bf0cf9fb60ae69b9a315c7d8007) Thanks [@openscript](https://github.com/openscript)! - Update from upstream + +## 1.8.2 + +### Patch Changes + +- [`e47dcdc`](https://github.com/changesets/action/commit/e47dcdcda2d3c570ab43fc31f1ac43872b47a5a4) Thanks [@openscript](https://github.com/openscript)! - Filter clientside for Forgejo + +## 1.8.1 + +### Patch Changes + +- [`2135466`](https://github.com/changesets/action/commit/213546601666ba543dd4a716db7b148c55f4c36f) Thanks [@openscript](https://github.com/openscript)! - Encode branch and base + ## 1.8.0 ### Minor Changes @@ -11,6 +37,7 @@ - [#502](https://github.com/changesets/action/pull/502) [`6002dbd`](https://github.com/changesets/action/commit/6002dbd987f49a3c0a134910d9c7bca975b79977) Thanks [@oshytiko](https://github.com/oshytiko)! - Fixed initial `.changeset` state being picked up, when `cwd` parameter is provided - [#536](https://github.com/changesets/action/pull/536) [`81b3f61`](https://github.com/changesets/action/commit/81b3f61ebffcb868f73e4c0b2682517149c834a2) Thanks [@radnan](https://github.com/radnan)! - Fixed `.changeset` state being picked for the version command when `cwd` parameter is provided +- [`905ea6d`](https://github.com/changesets/action/commit/905ea6d481f54d210f11da2daa15655dadd89f15) Thanks [@openscript](https://github.com/openscript)! - Add Forgejo/Gitea actions support ## 1.7.0 diff --git a/package.json b/package.json index 6d560e55..dc37dfa8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@changesets/action", - "version": "1.8.0", + "version": "1.8.4", "license": "MIT", "type": "module", "main": "dist/index.js", diff --git a/src/run.test.ts b/src/run.test.ts index ff1b268c..eaf1cb57 100644 --- a/src/run.test.ts +++ b/src/run.test.ts @@ -5,7 +5,7 @@ import { createFixture } from "fs-fixture"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { Git } from "./git.ts"; import { setupOctokit } from "./octokit.ts"; -import { runVersion } from "./run.ts"; +import { isForgejoOrGitea, runVersion } from "./run.ts"; vi.mock("@actions/github", () => ({ context: { @@ -427,3 +427,23 @@ fluminis divesque vulnere aquis parce lapsis rabie si visa fulmineis. expect(mockedGraphql.mock.calls[0]).toMatchSnapshot(); }); }); + +describe("isForgejoOrGitea", () => { + it("returns false when no Forgejo/Gitea env vars are set", () => { + delete process.env.GITEA_ACTIONS; + delete process.env.FORGEJO_ACTIONS; + expect(isForgejoOrGitea()).toBe(false); + }); + + it("returns true when GITEA_ACTIONS is set", () => { + process.env.GITEA_ACTIONS = "true"; + expect(isForgejoOrGitea()).toBe(true); + delete process.env.GITEA_ACTIONS; + }); + + it("returns true when FORGEJO_ACTIONS is set", () => { + process.env.FORGEJO_ACTIONS = "true"; + expect(isForgejoOrGitea()).toBe(true); + delete process.env.FORGEJO_ACTIONS; + }); +}); diff --git a/src/run.ts b/src/run.ts index 818edfb6..e8de52d9 100644 --- a/src/run.ts +++ b/src/run.ts @@ -20,6 +20,70 @@ import { const require = createRequire(import.meta.url); +/** + * Detects if we're running on Forgejo or Gitea Actions. + * These platforms set specific environment variables when running actions. + */ +export function isForgejoOrGitea(): boolean { + return !!(process.env.GITEA_ACTIONS || process.env.FORGEJO_ACTIONS); +} + +type PullRequestData = { + number: number; + node_id: string; + title: string; + body: string | null; + state: string; + head?: { ref: string }; + base?: { ref: string }; +}; + +/** + * Fetches existing pull requests from the version branch to the base branch. + * Uses different API endpoints for GitHub vs Forgejo/Gitea. + * + * GitHub: GET /repos/{owner}/{repo}/pulls?state=open&head={owner}:{branch}&base={base} + * Forgejo/Gitea: List all open PRs and filter client-side (their list API doesn't support head/base filters) + */ +export async function getExistingPullRequests( + octokit: Octokit, + options: { + owner: string; + repo: string; + head: string; + base: string; + }, +): Promise { + if (isForgejoOrGitea()) { + core.info("Detected Forgejo/Gitea environment, using compatible API"); + // Forgejo/Gitea list endpoint doesn't support head/base filters, + // so we list all open PRs and filter client-side + const response = await octokit.rest.pulls.list({ + owner: options.owner, + repo: options.repo, + state: "open", + }); + // Filter by head and base branch + const filtered = response.data.filter( + (pr) => pr.head.ref === options.head && pr.base.ref === options.base, + ); + core.info( + `Found ${response.data.length} open PR(s), ${filtered.length} matching head=${options.head} base=${options.base}`, + ); + return filtered; + } else { + // GitHub API: GET /repos/{owner}/{repo}/pulls with query params + const response = await octokit.rest.pulls.list({ + owner: options.owner, + repo: options.repo, + state: "open", + head: `${options.owner}:${options.head}`, + base: options.base, + }); + return response.data; + } +} + // GitHub Issues/PRs messages have a max size limit on the // message body payload. // `body is too long (maximum is 65536 characters)`. @@ -71,12 +135,12 @@ type PublishedPackage = { name: string; version: string }; type PublishResult = | { - published: true; - publishedPackages: PublishedPackage[]; - } + published: true; + publishedPackages: PublishedPackage[]; + } | { - published: false; - }; + published: false; + }; export async function runPublish({ script, @@ -108,7 +172,7 @@ export async function runPublish({ if (pkg === undefined) { throw new Error( `Package "${pkgName}" not found.` + - "This is probably a bug in the action, please open an issue", + "This is probably a bug in the action, please open an issue", ); } releasedPackages.push(pkg); @@ -127,7 +191,7 @@ export async function runPublish({ if (packages.length === 0) { throw new Error( `No package found.` + - "This is probably a bug in the action, please open an issue", + "This is probably a bug in the action, please open an issue", ); } let pkg = packages[0]; @@ -199,11 +263,10 @@ export async function getVersionPrBody({ prBodyMaxCharacters, branch, }: GetMessageOptions) { - let messageHeader = `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 ${ - hasPublishScript + let messageHeader = `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 ${hasPublishScript ? `the packages will be published to npm automatically` : `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 ${branch}, this PR will be updated. + }. If you're not ready to do a release yet, that's fine, whenever you add more changesets to ${branch}, this PR will be updated. `; let messagePrestate = !!preState ? `⚠️⚠️⚠️⚠️⚠️⚠️ @@ -330,9 +393,8 @@ export async function runVersion({ ); const finalPrTitle = `${prTitle}${!!preState ? ` (${preState.tag})` : ""}`; - const finalCommitMessage = `${commitMessage}${ - !!preState ? ` (${preState.tag})` : "" - }`; + const finalCommitMessage = `${commitMessage}${!!preState ? ` (${preState.tag})` : "" + }`; /** * Fetch any existing pull requests that are open against the branch, @@ -341,18 +403,14 @@ export async function runVersion({ * (`@changesets/ghcommit` has to reset the branch to the same commit as the base, * which GitHub will then react to by closing the PRs) */ - const existingPullRequests = await octokit.rest.pulls.list({ - ...github.context.repo, - state: "open", - head: `${github.context.repo.owner}:${versionBranch}`, + const existingPullRequests = await getExistingPullRequests(octokit, { + owner: github.context.repo.owner, + repo: github.context.repo.repo, + head: versionBranch, base: branch, }); core.info( - `Existing pull requests: ${JSON.stringify( - existingPullRequests.data, - null, - 2, - )}`, + `Existing pull requests: ${JSON.stringify(existingPullRequests, null, 2)}`, ); await git.pushChanges({ branch: versionBranch, message: finalCommitMessage }); @@ -369,7 +427,7 @@ export async function runVersion({ prBodyMaxCharacters, }); - if (existingPullRequests.data.length === 0) { + if (existingPullRequests.length === 0) { core.info("creating pull request"); const { data: newPullRequest } = await octokit.rest.pulls.create({ base: branch, @@ -384,50 +442,63 @@ export async function runVersion({ pullRequestNumber: newPullRequest.number, }; } else { - const [pullRequest] = existingPullRequests.data; + const [pullRequest] = existingPullRequests; core.info(`updating found pull request #${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 - } + if (isForgejoOrGitea()) { + // Forgejo/Gitea does not support the GitHub GraphQL API. + // Use the REST endpoint to update the PR instead. + await octokit.rest.pulls.update({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + pull_number: pullRequest.number, + title: finalPrTitle, + body: prBody, + state: "open", + }); + } else { + const convertPullRequestToDraftMutation = + prDraft === "always" + ? ` + convertPullRequestToDraft( + input: { + pullRequestId: $pullRequestId + } + ) { + pullRequest { + id + } + }` + : ""; + const updatePullRequestMutation = ` + mutation UpdatePullRequest( + $pullRequestId: ID! + $title: String! + $body: String! ) { - pullRequest { - id + ${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, - }); + await octokit.graphql(updatePullRequestMutation, { + pullRequestId: pullRequest.node_id, + title: finalPrTitle, + body: prBody, + }); + } return { pullRequestNumber: pullRequest.number,