Skip to content

Commit df020e8

Browse files
matt-aitkenclaude
andcommitted
core: move sanitizeBranchName + isValidGitBranchName to @trigger.dev/core/v3/utils/gitBranch
Both helpers were originally in apps/webapp/app/v3/gitBranch.ts. internal-packages/rbac needed sanitizeBranchName for the PREVIEW-env branch resolution added in 8246234, and copy-pasting the function into the fallback was a smell. Moves the canonical home into core (no internal-package can import webapp code), and updates the four webapp call sites + the rbac fallback to import from @trigger.dev/core/v3/utils/gitBranch. `sanitizeBranchName`'s input type is widened slightly to `string | null | undefined` so callers passing `Headers.get(...)` (which returns `string | null`) don't need a `?? undefined` workaround. The existing webapp callers all pass `string | undefined`; the new union is backwards-compatible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8246234 commit df020e8

8 files changed

Lines changed: 23 additions & 34 deletions

File tree

.changeset/git-branch-utils.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/core": patch
3+
---
4+
5+
Add `sanitizeBranchName` and `isValidGitBranchName` exports under `@trigger.dev/core/v3/utils/gitBranch`. These were previously webapp-internal but are now shared with the RBAC fallback's branch-aware authentication path.

apps/webapp/app/models/runtimeEnvironment.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Prisma, PrismaClientOrTransaction, RuntimeEnvironment } from "@tri
33
import { $replica, prisma } from "~/db.server";
44
import { logger } from "~/services/logger.server";
55
import { getUsername } from "~/utils/username";
6-
import { sanitizeBranchName } from "~/v3/gitBranch";
6+
import { sanitizeBranchName } from "@trigger.dev/core/v3/utils/gitBranch";
77

88
export type { RuntimeEnvironment };
99

apps/webapp/app/services/apiAuth.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
isOrganizationAccessToken,
2424
} from "./organizationAccessToken.server";
2525
import { isPublicJWT, validatePublicJwtKey } from "./realtime/jwtAuth.server";
26-
import { sanitizeBranchName } from "~/v3/gitBranch";
26+
import { sanitizeBranchName } from "@trigger.dev/core/v3/utils/gitBranch";
2727

2828
const ClaimsSchema = z.object({
2929
scopes: z.array(z.string()).optional(),

apps/webapp/app/services/upsertBranch.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import slug from "slug";
33
import { prisma } from "~/db.server";
44
import { createApiKeyForEnv, createPkApiKeyForEnv } from "~/models/api-key.server";
55
import { type CreateBranchOptions } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route";
6-
import { isValidGitBranchName, sanitizeBranchName } from "~/v3/gitBranch";
6+
import { isValidGitBranchName, sanitizeBranchName } from "@trigger.dev/core/v3/utils/gitBranch";
77
import { logger } from "./logger.server";
88
import { getCurrentPlan, getLimit } from "./platform.v3.server";
99

apps/webapp/test/validateGitBranchName.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from "vitest";
2-
import { isValidGitBranchName, sanitizeBranchName } from "~/v3/gitBranch";
2+
import { isValidGitBranchName, sanitizeBranchName } from "@trigger.dev/core/v3/utils/gitBranch";
33

44
describe("isValidGitBranchName", () => {
55
it("returns true for a valid branch name", async () => {

internal-packages/rbac/src/fallback.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
import { createHash } from "node:crypto";
1616
import type { PrismaClient } from "@trigger.dev/database";
1717
import { validateJWT } from "@trigger.dev/core/v3/jwt";
18+
import { sanitizeBranchName } from "@trigger.dev/core/v3/utils/gitBranch";
1819
import { buildFallbackAbility, buildJwtAbility, permissiveAbility } from "./ability.js";
1920

2021
export class RoleBaseAccessFallback {
@@ -311,22 +312,6 @@ class RoleBaseAccessFallbackController implements RoleBaseAccessController {
311312
}
312313
}
313314

314-
// Mirror of `apps/webapp/app/v3/gitBranch.ts#sanitizeBranchName`.
315-
// Inlined here because internal-packages can't import webapp code; the
316-
// two should stay in sync. Strips common refs/* prefixes and rejects
317-
// unknown ref formats (returns undefined → no branch override).
318-
function sanitizeBranchName(ref: string | null): string | undefined {
319-
if (!ref) return undefined;
320-
if (ref.startsWith("refs/heads/")) return ref.substring("refs/heads/".length);
321-
if (ref.startsWith("refs/remotes/")) return ref.substring("refs/remotes/".length);
322-
if (ref.startsWith("refs/tags/")) return ref.substring("refs/tags/".length);
323-
if (ref.startsWith("refs/pull/")) return ref.substring("refs/pull/".length);
324-
if (ref.startsWith("refs/merge/")) return ref.substring("refs/merge/".length);
325-
if (ref.startsWith("refs/release/")) return ref.substring("refs/release/".length);
326-
if (ref.startsWith("refs/")) return undefined;
327-
return ref;
328-
}
329-
330315
function isPublicJWT(token: string): boolean {
331316
const parts = token.split(".");
332317
if (parts.length !== 3) return false;

packages/core/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"./v3/semanticInternalAttributes": "./src/v3/semanticInternalAttributes.ts",
3838
"./v3/utils/durations": "./src/v3/utils/durations.ts",
3939
"./v3/utils/flattenAttributes": "./src/v3/utils/flattenAttributes.ts",
40+
"./v3/utils/gitBranch": "./src/v3/utils/gitBranch.ts",
4041
"./v3/utils/ioSerialization": "./src/v3/utils/ioSerialization.ts",
4142
"./v3/utils/omit": "./src/v3/utils/omit.ts",
4243
"./v3/utils/retries": "./src/v3/utils/retries.ts",
@@ -402,6 +403,17 @@
402403
"default": "./dist/commonjs/v3/utils/flattenAttributes.js"
403404
}
404405
},
406+
"./v3/utils/gitBranch": {
407+
"import": {
408+
"@triggerdotdev/source": "./src/v3/utils/gitBranch.ts",
409+
"types": "./dist/esm/v3/utils/gitBranch.d.ts",
410+
"default": "./dist/esm/v3/utils/gitBranch.js"
411+
},
412+
"require": {
413+
"types": "./dist/commonjs/v3/utils/gitBranch.d.ts",
414+
"default": "./dist/commonjs/v3/utils/gitBranch.js"
415+
}
416+
},
405417
"./v3/utils/ioSerialization": {
406418
"import": {
407419
"@triggerdotdev/source": "./src/v3/utils/ioSerialization.ts",
Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,30 @@
11
export function isValidGitBranchName(branch: string): boolean {
2-
// Must not be empty
32
if (!branch) return false;
43

5-
// Disallowed characters: space, ~, ^, :, ?, *, [, \
64
if (/[ \~\^:\?\*\[\\]/.test(branch)) return false;
75

8-
// Disallow ASCII control characters (0-31) and DEL (127)
96
for (let i = 0; i < branch.length; i++) {
107
const code = branch.charCodeAt(i);
118
if ((code >= 0 && code <= 31) || code === 127) return false;
129
}
1310

14-
// Cannot start or end with a slash
1511
if (branch.startsWith("/") || branch.endsWith("/")) return false;
16-
17-
// Cannot have consecutive slashes
1812
if (branch.includes("//")) return false;
19-
20-
// Cannot contain '..'
2113
if (branch.includes("..")) return false;
22-
23-
// Cannot contain '@{'
2414
if (branch.includes("@{")) return false;
25-
26-
// Cannot end with '.lock'
2715
if (branch.endsWith(".lock")) return false;
2816

2917
return true;
3018
}
3119

32-
export function sanitizeBranchName(ref: string | undefined): string | null {
20+
export function sanitizeBranchName(ref: string | null | undefined): string | null {
3321
if (!ref) return null;
3422
if (ref.startsWith("refs/heads/")) return ref.substring("refs/heads/".length);
3523
if (ref.startsWith("refs/remotes/")) return ref.substring("refs/remotes/".length);
3624
if (ref.startsWith("refs/tags/")) return ref.substring("refs/tags/".length);
3725
if (ref.startsWith("refs/pull/")) return ref.substring("refs/pull/".length);
3826
if (ref.startsWith("refs/merge/")) return ref.substring("refs/merge/".length);
3927
if (ref.startsWith("refs/release/")) return ref.substring("refs/release/".length);
40-
//unknown ref format, so reject
4128
if (ref.startsWith("refs/")) return null;
4229

4330
return ref;

0 commit comments

Comments
 (0)