1- import { GitMeta , } from "@trigger.dev/core/v3" ;
1+ import { GitMeta } from "@trigger.dev/core/v3" ;
22import { DEFAULT_DEV_BRANCH } from "@trigger.dev/core/v3/utils/gitBranch" ;
33import { type RuntimeEnvironmentType } from "@trigger.dev/database" ;
44import { type z } from "zod" ;
@@ -10,13 +10,46 @@ import { getCurrentPlan, getPlans } from "~/services/platform.v3.server";
1010import { checkBranchLimit } from "~/services/upsertBranch.server" ;
1111import { devPresence } from "./DevPresence.server" ;
1212import { sortEnvironments } from "~/utils/environmentSort" ;
13- import { toBranchableEnvironmentType } from "~/utils/branchableEnvironment" ;
13+ import {
14+ type BranchableEnvironmentType ,
15+ toBranchableEnvironmentType ,
16+ } from "~/utils/branchableEnvironment" ;
1417
1518type Result = Awaited < ReturnType < BranchesPresenter [ "call" ] > > ;
1619export type Branch = Result [ "branches" ] [ number ] ;
1720
1821const BRANCHES_PER_PAGE = 25 ;
1922
23+ /**
24+ * Prisma `where` fragment that scopes the branches list by branch name, keyed by
25+ * environment type. Spread it into the query's `where` (it contributes either a
26+ * `branchName` constraint or a top-level `OR`).
27+ *
28+ * The default DEV branch is the root dev env, stored with `branchName: null`, so
29+ * for DEVELOPMENT we always include the null-branchName root (and still match it
30+ * when searching — hence the top-level `OR`, since a scalar field filter can't
31+ * express "matches search OR is null"). PREVIEW only ever lists real branches, so
32+ * its root (null) is excluded. Passing no `search` yields the "all branches of
33+ * this type" fragment.
34+ */
35+ function branchNameFilter (
36+ envType : BranchableEnvironmentType ,
37+ search ?: string
38+ ) : Prisma . RuntimeEnvironmentWhereInput {
39+ switch ( envType ) {
40+ case "DEVELOPMENT" :
41+ return search
42+ ? { OR : [ { branchName : { contains : search , mode : "insensitive" } } , { branchName : null } ] }
43+ : { } ;
44+ case "PREVIEW" :
45+ return search
46+ ? { branchName : { contains : search , mode : "insensitive" } }
47+ : { branchName : { not : null } } ;
48+ default :
49+ throw new Error ( `branchNameFilter: unsupported environment type "${ envType } "` ) ;
50+ }
51+ }
52+
2053type Options = z . infer < typeof BranchesOptions > ;
2154
2255export type GitMetaLinks = {
@@ -133,30 +166,26 @@ export class BranchesPresenter {
133166 } ;
134167 }
135168
136- // The default DEV branch has no branchName (it's the root dev env, stored
137- // with branchName: null), so searching for it by name wouldn't display it.
138- // Hacky way around that: always include the null-branchName root env.
139- const branchNameWhere = envType === "DEVELOPMENT" ?
140- search
141- ? { OR : [ { contains : search , mode : "insensitive" as const } , { is : null } ] }
142- : { } :
143- search
144- ? { contains : search , mode : "insensitive" as const }
145- : { not : null } ;
169+ const branchNameWhere = branchNameFilter ( envType , search ) ;
146170 const orgMemberWhere = envType === "DEVELOPMENT" ? { orgMember : { userId } } : { } ;
147171
148-
149172 const visibleCount = await this . #prismaClient. runtimeEnvironment . count ( {
150173 where : {
151174 projectId : project . id ,
152175 type : envType ,
153- branchName : branchNameWhere ,
176+ ... branchNameWhere ,
154177 ...orgMemberWhere ,
155178 ...( showArchived ? { } : { archivedAt : null } ) ,
156179 } ,
157180 } ) ;
158181
159- const limits = await checkBranchLimit ( { prisma : this . #prismaClient, organizationId : project . organizationId , projectId : project . id , userId, type : envType } ) ;
182+ const limits = await checkBranchLimit ( {
183+ prisma : this . #prismaClient,
184+ organizationId : project . organizationId ,
185+ projectId : project . id ,
186+ userId,
187+ type : envType ,
188+ } ) ;
160189
161190 const [ currentPlan , plans ] = await Promise . all ( [
162191 getCurrentPlan ( project . organizationId ) ,
@@ -179,12 +208,13 @@ export class BranchesPresenter {
179208 type : true ,
180209 archivedAt : true ,
181210 createdAt : true ,
211+ updatedAt : true ,
182212 git : true ,
183213 } ,
184214 where : {
185215 projectId : project . id ,
186216 type : envType ,
187- branchName : branchNameWhere ,
217+ ... branchNameWhere ,
188218 ...orgMemberWhere ,
189219 ...( showArchived ? { } : { archivedAt : null } ) ,
190220 } ,
@@ -195,17 +225,15 @@ export class BranchesPresenter {
195225 take : BRANCHES_PER_PAGE ,
196226 } ) ;
197227
198- const totalBranchesWhere = envType === "DEVELOPMENT" ? { } : { not : null } ;
199228 const totalBranches = await this . #prismaClient. runtimeEnvironment . count ( {
200229 where : {
201230 projectId : project . id ,
202231 type : envType ,
203- branchName : totalBranchesWhere ,
232+ ... branchNameFilter ( envType ) ,
204233 ...orgMemberWhere ,
205234 } ,
206235 } ) ;
207236
208-
209237 const branchesFiltered = branches
210238 . filter ( ( branch ) => envType === "DEVELOPMENT" || branch . branchName !== null )
211239 . map ( ( branch ) => ( {
@@ -234,8 +262,13 @@ export class BranchesPresenter {
234262 }
235263}
236264
237- export async function hydrateEnvsWithActivity < T extends { type : RuntimeEnvironmentType ; id : string } >
238- ( userId : string , projectId : string , environments : T [ ] ) : Promise < Array < T & { lastActivity : Date | undefined ; isConnected : boolean | undefined } > > {
265+ export async function hydrateEnvsWithActivity <
266+ T extends { type : RuntimeEnvironmentType ; id : string }
267+ > (
268+ userId : string ,
269+ projectId : string ,
270+ environments : T [ ]
271+ ) : Promise < Array < T & { lastActivity : Date | undefined ; isConnected : boolean | undefined } > > {
239272 const recentDevBranchIds = await devPresence . getRecentBranchIds ( userId , projectId ) ;
240273
241274 // Resolve presence for all recently-active dev branches in a single MGET
0 commit comments