diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index 66149eac3..f4a128953 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -20,6 +20,7 @@ import { import type { ParsedGitHubContext } from "../github/context"; import type { CommonFields, PreparedContext, EventData } from "./types"; import { GITHUB_SERVER_URL } from "../github/api/config"; +import { getNoreplyEmailDomain } from "../utils/noreply-email"; import type { Mode, ModeContext } from "../modes/types"; import { extractUserRequest } from "../utils/extract-user-request"; export type { CommonFields, PreparedContext } from "./types"; @@ -402,9 +403,12 @@ function getCommitInstructions( context: PreparedContext, useCommitSigning: boolean, ): string { + // Determine the noreply email domain based on GITHUB_SERVER_URL (for GHES support) + const noreplyDomain = getNoreplyEmailDomain(GITHUB_SERVER_URL); + const coAuthorLine = (githubData.triggerDisplayName ?? context.triggerUsername !== "Unknown") - ? `Co-authored-by: ${githubData.triggerDisplayName ?? context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>` + ? `Co-authored-by: ${githubData.triggerDisplayName ?? context.triggerUsername} <${context.triggerUsername}@${noreplyDomain}>` : ""; if (useCommitSigning) { diff --git a/src/github/operations/git-config.ts b/src/github/operations/git-config.ts index 733744f51..d53f621c8 100644 --- a/src/github/operations/git-config.ts +++ b/src/github/operations/git-config.ts @@ -11,6 +11,7 @@ import { join } from "path"; import { homedir } from "os"; import type { GitHubContext } from "../context"; import { GITHUB_SERVER_URL } from "../api/config"; +import { getNoreplyEmailDomain } from "../../utils/noreply-email"; const SSH_SIGNING_KEY_PATH = join(homedir(), ".ssh", "claude_signing_key"); @@ -28,10 +29,7 @@ export async function configureGitAuth( // Determine the noreply email domain based on GITHUB_SERVER_URL const serverUrl = new URL(GITHUB_SERVER_URL); - const noreplyDomain = - serverUrl.hostname === "github.com" - ? "users.noreply.github.com" - : `users.noreply.${serverUrl.hostname}`; + const noreplyDomain = getNoreplyEmailDomain(GITHUB_SERVER_URL); // Configure git user console.log("Configuring git user..."); diff --git a/src/utils/noreply-email.ts b/src/utils/noreply-email.ts new file mode 100644 index 000000000..98cbaf1c4 --- /dev/null +++ b/src/utils/noreply-email.ts @@ -0,0 +1,20 @@ +/** + * Utility for generating GitHub noreply email domains + * Handles both github.com and GitHub Enterprise Server (GHES) instances + */ + +/** + * Get the noreply email domain for a GitHub server URL + * + * For github.com: returns "users.noreply.github.com" + * For GHES (e.g., ghes.example.com): returns "users.noreply.ghes.example.com" + * + * @param serverUrl - The GitHub server URL (e.g., "https://github.com" or "https://ghes.example.com") + * @returns The noreply email domain + */ +export function getNoreplyEmailDomain(serverUrl: string): string { + const url = new URL(serverUrl); + return url.hostname === "github.com" + ? "users.noreply.github.com" + : `users.noreply.${url.hostname}`; +} diff --git a/test/noreply-email.test.ts b/test/noreply-email.test.ts new file mode 100644 index 000000000..e78083e73 --- /dev/null +++ b/test/noreply-email.test.ts @@ -0,0 +1,54 @@ +#!/usr/bin/env bun + +import { describe, test, expect } from "bun:test"; +import { getNoreplyEmailDomain } from "../src/utils/noreply-email"; + +describe("getNoreplyEmailDomain", () => { + test("returns users.noreply.github.com for github.com", () => { + expect(getNoreplyEmailDomain("https://github.com")).toBe( + "users.noreply.github.com", + ); + }); + + test("returns users.noreply.github.com for github.com with trailing slash", () => { + expect(getNoreplyEmailDomain("https://github.com/")).toBe( + "users.noreply.github.com", + ); + }); + + test("returns GHES noreply domain for enterprise server", () => { + expect(getNoreplyEmailDomain("https://ghes.example.com")).toBe( + "users.noreply.ghes.example.com", + ); + }); + + test("returns GHES noreply domain for enterprise server with trailing slash", () => { + expect(getNoreplyEmailDomain("https://ghes.example.com/")).toBe( + "users.noreply.ghes.example.com", + ); + }); + + test("returns GHES noreply domain for enterprise server with port", () => { + expect(getNoreplyEmailDomain("https://ghes.example.com:8443")).toBe( + "users.noreply.ghes.example.com", + ); + }); + + test("returns GHES noreply domain for enterprise server with path", () => { + expect(getNoreplyEmailDomain("https://ghes.example.com/api/v3")).toBe( + "users.noreply.ghes.example.com", + ); + }); + + test("handles subdomain enterprise servers", () => { + expect(getNoreplyEmailDomain("https://github.mycompany.com")).toBe( + "users.noreply.github.mycompany.com", + ); + }); + + test("handles deep subdomain enterprise servers", () => { + expect(getNoreplyEmailDomain("https://git.internal.corp.example.com")).toBe( + "users.noreply.git.internal.corp.example.com", + ); + }); +});