diff --git a/packages/server/src/utils/worktree.test.ts b/packages/server/src/utils/worktree.test.ts index 4750bb68a..172da2cf8 100644 --- a/packages/server/src/utils/worktree.test.ts +++ b/packages/server/src/utils/worktree.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { createWorktree } from "./worktree"; +import { createWorktree, slugify } from "./worktree"; import { execSync } from "child_process"; import { mkdtempSync, rmSync, existsSync, realpathSync, writeFileSync, readFileSync } from "fs"; import { join } from "path"; @@ -154,3 +154,24 @@ describe("createWorktree", () => { expect(existsSync(expectedWorktreePath)).toBe(false); }); }); + +describe("slugify", () => { + it("converts to lowercase kebab-case", () => { + expect(slugify("Hello World")).toBe("hello-world"); + expect(slugify("FOO_BAR")).toBe("foo-bar"); + }); + + it("truncates long strings at word boundary", () => { + const longInput = "https-stackoverflow-com-questions-68349031-only-run-actions-on-non-draft-pull-request"; + const result = slugify(longInput); + expect(result.length).toBeLessThanOrEqual(50); + expect(result).toBe("https-stackoverflow-com-questions-68349031-only"); + }); + + it("truncates without trailing hyphen when no word boundary", () => { + const longInput = "a".repeat(60); + const result = slugify(longInput); + expect(result.length).toBe(50); + expect(result.endsWith("-")).toBe(false); + }); +}); diff --git a/packages/server/src/utils/worktree.ts b/packages/server/src/utils/worktree.ts index 13c2cdbba..193be9844 100644 --- a/packages/server/src/utils/worktree.ts +++ b/packages/server/src/utils/worktree.ts @@ -156,14 +156,28 @@ export function validateBranchSlug(slug: string): { return { valid: true }; } +const MAX_SLUG_LENGTH = 50; + /** * Convert string to kebab-case for branch names */ export function slugify(input: string): string { - return input + const slug = input .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); + + if (slug.length <= MAX_SLUG_LENGTH) { + return slug; + } + + // Truncate at word boundary (hyphen) if possible + const truncated = slug.slice(0, MAX_SLUG_LENGTH); + const lastHyphen = truncated.lastIndexOf("-"); + if (lastHyphen > MAX_SLUG_LENGTH / 2) { + return truncated.slice(0, lastHyphen); + } + return truncated.replace(/-+$/, ""); } function sanitizeWorktreeSlug(input: string): string {