diff --git a/README.md b/README.md index b3aad36..c54cd9c 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,31 @@ This action has been enhanced to work with Gitea installations. The main differe 2. **API URL Configuration**: You must specify your Gitea server URL using the `gitea_api_url` input. +3. **Custom Server URL**: For Gitea instances running in containers, you can override link generation using the `GITEA_SERVER_URL` environment variable. + +### Custom Server URL Configuration + +When running Gitea in containers, the action may generate links using internal container URLs (e.g., `http://gitea:3000`) instead of your public URL. To fix this, set the `GITEA_SERVER_URL` environment variable: + +```yaml +- uses: markwylde/claude-code-gitea-action@v1.0.5 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + gitea_token: ${{ secrets.GITEA_TOKEN }} + env: + # Override the internal container URL with your public URL + GITEA_SERVER_URL: https://gitea.example.com +``` + +**How it works:** +- The action first checks for `GITEA_SERVER_URL` (user-configurable) +- Falls back to `GITHUB_SERVER_URL` (automatically set by Gitea Actions) +- Uses `https://github.com` as final fallback + +This ensures that all links in Claude's comments (job runs, branches, etc.) point to your public Gitea instance instead of internal container addresses. + +See [`examples/gitea-custom-url.yml`](./examples/gitea-custom-url.yml) for a complete example. + ### Gitea Setup Notes - Use a Gitea personal access token "GITEA_TOKEN" diff --git a/examples/gitea-custom-url.yml b/examples/gitea-custom-url.yml new file mode 100644 index 0000000..92051e7 --- /dev/null +++ b/examples/gitea-custom-url.yml @@ -0,0 +1,23 @@ +name: Claude PR Review with Custom Gitea URL +on: + pull_request: + types: [opened, synchronize] + issue_comment: + types: [created] + +jobs: + claude-review: + runs-on: ubuntu-latest + steps: + - name: Claude Code Analysis + uses: markwylde/claude-code-gitea-action@gitea + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + claude-api-key: ${{ secrets.CLAUDE_API_KEY }} + env: + # Set this to your public Gitea URL to override the internal container URL + # This ensures that links in comments point to the correct public URL + GITEA_SERVER_URL: https://gitea.example.com + + # Note: GITHUB_SERVER_URL is automatically set by Gitea Actions to the internal URL + # but it will be overridden by GITEA_SERVER_URL if set above diff --git a/package-lock.json b/package-lock.json index 0b78f60..a92835b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,6 @@ "@actions/github": "^6.0.1", "@anthropic-ai/sdk": "^0.30.0", "@modelcontextprotocol/sdk": "^1.11.0", - "@octokit/graphql": "^8.2.2", - "@octokit/rest": "^21.1.1", "@octokit/webhooks-types": "^7.6.1", "node-fetch": "^3.3.2", "zod": "^3.24.4" @@ -211,82 +209,6 @@ "node": ">= 18" } }, - "node_modules/@octokit/graphql": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", - "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql/node_modules/@octokit/endpoint": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", - "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", - "license": "MIT" - }, - "node_modules/@octokit/graphql/node_modules/@octokit/request": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.3.tgz", - "integrity": "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.1.4", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql/node_modules/@octokit/request-error": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", - "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql/node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, - "node_modules/@octokit/graphql/node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "license": "ISC" - }, "node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", @@ -382,185 +304,6 @@ "node": ">= 18" } }, - "node_modules/@octokit/rest": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", - "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", - "license": "MIT", - "dependencies": { - "@octokit/core": "^6.1.4", - "@octokit/plugin-paginate-rest": "^11.4.2", - "@octokit/plugin-request-log": "^5.3.1", - "@octokit/plugin-rest-endpoint-methods": "^13.3.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/core": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz", - "integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.2.2", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/endpoint": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", - "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/endpoint/node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", - "license": "MIT" - }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz", - "integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", - "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz", - "integrity": "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/request": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.3.tgz", - "integrity": "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.1.4", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/request-error": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", - "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/request-error/node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/request/node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, - "node_modules/@octokit/rest/node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "license": "Apache-2.0" - }, - "node_modules/@octokit/rest/node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "license": "ISC" - }, "node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", @@ -1042,22 +785,6 @@ "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, - "node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/src/github/api/config.ts b/src/github/api/config.ts index 9daddae..5776532 100644 --- a/src/github/api/config.ts +++ b/src/github/api/config.ts @@ -7,8 +7,25 @@ function deriveApiUrl(serverUrl: string): string { return `${serverUrl}/api/v1`; } -export const GITEA_SERVER_URL = - process.env.GITHUB_SERVER_URL || "https://github.com"; +// Get the appropriate server URL, prioritizing GITEA_SERVER_URL for custom Gitea instances +function getServerUrl(): string { + // First check for GITEA_SERVER_URL (can be set by user) + const giteaServerUrl = process.env.GITEA_SERVER_URL; + if (giteaServerUrl && giteaServerUrl !== "") { + return giteaServerUrl; + } + + // Fall back to GITHUB_SERVER_URL (set by Gitea/GitHub Actions environment) + const githubServerUrl = process.env.GITHUB_SERVER_URL; + if (githubServerUrl && githubServerUrl !== "") { + return githubServerUrl; + } + + // Default fallback + return "https://github.com"; +} + +export const GITEA_SERVER_URL = getServerUrl(); export const GITEA_API_URL = process.env.GITEA_API_URL || deriveApiUrl(GITEA_SERVER_URL); diff --git a/test/gitea-server-url.test.ts b/test/gitea-server-url.test.ts new file mode 100644 index 0000000..fd3adb4 --- /dev/null +++ b/test/gitea-server-url.test.ts @@ -0,0 +1,83 @@ +import { describe, it, expect, beforeEach, afterEach } from "bun:test"; + +describe("GITEA_SERVER_URL configuration", () => { + const originalEnv = process.env; + + beforeEach(() => { + // Reset environment variables + process.env = { ...originalEnv }; + delete process.env.GITEA_SERVER_URL; + delete process.env.GITHUB_SERVER_URL; + + // Clear module cache to force re-evaluation + delete require.cache[require.resolve("../src/github/api/config")]; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should prioritize GITEA_SERVER_URL over GITHUB_SERVER_URL", async () => { + process.env.GITEA_SERVER_URL = "https://gitea.example.com"; + process.env.GITHUB_SERVER_URL = "http://gitea:3000"; + + const { GITEA_SERVER_URL } = await import("../src/github/api/config"); + expect(GITEA_SERVER_URL).toBe("https://gitea.example.com"); + }); + + it("should fall back to GITHUB_SERVER_URL when GITEA_SERVER_URL is not set", async () => { + process.env.GITHUB_SERVER_URL = "http://gitea:3000"; + + const { GITEA_SERVER_URL } = await import("../src/github/api/config"); + expect(GITEA_SERVER_URL).toBe("http://gitea:3000"); + }); + + it("should use default when neither GITEA_SERVER_URL nor GITHUB_SERVER_URL is set", async () => { + const { GITEA_SERVER_URL } = await import("../src/github/api/config"); + expect(GITEA_SERVER_URL).toBe("https://github.com"); + }); + + it("should ignore empty GITEA_SERVER_URL and use GITHUB_SERVER_URL", async () => { + process.env.GITEA_SERVER_URL = ""; + process.env.GITHUB_SERVER_URL = "http://gitea:3000"; + + const { GITEA_SERVER_URL } = await import("../src/github/api/config"); + expect(GITEA_SERVER_URL).toBe("http://gitea:3000"); + }); + + it("should derive correct API URL from custom GITEA_SERVER_URL", async () => { + process.env.GITEA_SERVER_URL = "https://gitea.example.com"; + + const { GITEA_API_URL } = await import("../src/github/api/config"); + expect(GITEA_API_URL).toBe("https://gitea.example.com/api/v1"); + }); + + it("should handle GitHub.com URLs correctly", async () => { + process.env.GITEA_SERVER_URL = "https://github.com"; + + const { GITEA_API_URL } = await import("../src/github/api/config"); + expect(GITEA_API_URL).toBe("https://api.github.com"); + }); + + it("should create correct job run links with custom GITEA_SERVER_URL", async () => { + process.env.GITEA_SERVER_URL = "https://gitea.example.com"; + + // Clear module cache and re-import + delete require.cache[require.resolve("../src/github/operations/comments/common")]; + const { createJobRunLink } = await import("../src/github/operations/comments/common"); + + const link = createJobRunLink("owner", "repo", "123"); + expect(link).toBe("[View job run](https://gitea.example.com/owner/repo/actions/runs/123)"); + }); + + it("should create correct branch links with custom GITEA_SERVER_URL", async () => { + process.env.GITEA_SERVER_URL = "https://gitea.example.com"; + + // Clear module cache and re-import + delete require.cache[require.resolve("../src/github/operations/comments/common")]; + const { createBranchLink } = await import("../src/github/operations/comments/common"); + + const link = createBranchLink("owner", "repo", "feature-branch"); + expect(link).toBe("\n[View branch](https://gitea.example.com/owner/repo/src/branch/feature-branch/)"); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index b84ba7b..9439602 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { // Environment setup & latest features - "lib": ["ESNext"], + "lib": ["ESNext", "DOM"], "target": "ESNext", "module": "ESNext", "moduleDetection": "force",