Skip to content

Commit 4e38716

Browse files
committed
feat(webapp,core): add an endpoint to list a project's environments
GET /api/v1/projects/{projectRef}/environments returns the base environments a personal access token can access for a project: the caller's own dev environment plus the project's staging, preview, and production environments. Preview branch children are excluded — it returns the branchable parent. Archived environments are filtered out and the order matches the dashboard's environment switcher.
1 parent df964ea commit 4e38716

3 files changed

Lines changed: 93 additions & 0 deletions

File tree

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 `GetProjectEnvironmentsResponseBody` and `ProjectEnvironment` schemas for the new `GET /api/v1/projects/{projectRef}/environments` endpoint, which lists the parent environments (dev, staging, preview, prod) a personal access token can access for a project. Dev is scoped to the token owner and branch (preview child) environments are excluded.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { json } from "@remix-run/server-runtime";
2+
import { type GetProjectEnvironmentsResponseBody } from "@trigger.dev/core/v3";
3+
import { z } from "zod";
4+
import { $replica } from "~/db.server";
5+
import { findProjectByRef } from "~/models/project.server";
6+
import { createLoaderPATApiRoute } from "~/services/routeBuilders/apiBuilder.server";
7+
import { sortEnvironments } from "~/utils/environmentSort";
8+
9+
const ParamsSchema = z.object({
10+
projectRef: z.string(),
11+
});
12+
13+
export const loader = createLoaderPATApiRoute(
14+
{
15+
params: ParamsSchema,
16+
corsStrategy: "all",
17+
// Resolve projectRef → org so the PAT plugin can ground its role-floor
18+
// calculation. Membership is enforced by the plugin (`authenticatePat`
19+
// rejects users who aren't members of the target org) and again by
20+
// `findProjectByRef` below.
21+
context: async (params) => {
22+
const project = await $replica.project.findFirst({
23+
where: { externalRef: params.projectRef },
24+
select: { organizationId: true },
25+
});
26+
return project ? { organizationId: project.organizationId } : {};
27+
},
28+
authorization: { action: "read", resource: () => ({ type: "environments" }) },
29+
},
30+
async ({ params, authentication }) => {
31+
const project = await findProjectByRef(params.projectRef, authentication.userId);
32+
33+
if (!project) {
34+
return json({ error: "Project not found" }, { status: 404 });
35+
}
36+
37+
const environments = await $replica.runtimeEnvironment.findMany({
38+
where: {
39+
projectId: project.id,
40+
// Only base/parent environments. Branch children (preview branches)
41+
// are excluded — syncs target the parent and branches override elsewhere.
42+
parentEnvironmentId: null,
43+
archivedAt: null,
44+
OR: [
45+
{ type: { in: ["STAGING", "PRODUCTION", "PREVIEW"] } },
46+
// dev is per-user: only return the caller's own dev environment
47+
{ type: "DEVELOPMENT", orgMember: { userId: authentication.userId } },
48+
],
49+
},
50+
select: {
51+
id: true,
52+
slug: true,
53+
type: true,
54+
isBranchableEnvironment: true,
55+
branchName: true,
56+
paused: true,
57+
},
58+
});
59+
60+
const result: GetProjectEnvironmentsResponseBody = sortEnvironments(environments).map((env) => ({
61+
id: env.id,
62+
slug: env.slug,
63+
type: env.type,
64+
isBranchableEnvironment: env.isBranchableEnvironment,
65+
branchName: env.branchName,
66+
paused: env.paused,
67+
}));
68+
69+
return json(result);
70+
}
71+
);

packages/core/src/v3/schemas/api.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@ export const GetProjectEnvResponse = z.object({
8181

8282
export type GetProjectEnvResponse = z.infer<typeof GetProjectEnvResponse>;
8383

84+
export const ProjectEnvironment = z.object({
85+
id: z.string(),
86+
/// The slug used as the environment identifier in env var endpoints (e.g. "dev", "stg", "prod", "preview")
87+
slug: z.string(),
88+
type: z.enum(["DEVELOPMENT", "STAGING", "PREVIEW", "PRODUCTION"]),
89+
/// Whether this is the branchable parent (preview); individual branches are not returned
90+
isBranchableEnvironment: z.boolean(),
91+
branchName: z.string().nullable(),
92+
paused: z.boolean(),
93+
});
94+
95+
export type ProjectEnvironment = z.infer<typeof ProjectEnvironment>;
96+
97+
export const GetProjectEnvironmentsResponseBody = z.array(ProjectEnvironment);
98+
99+
export type GetProjectEnvironmentsResponseBody = z.infer<typeof GetProjectEnvironmentsResponseBody>;
100+
84101
// Zod schema for the response body type
85102
export const GetWorkerTaskResponse = z.object({
86103
id: z.string(),

0 commit comments

Comments
 (0)