df78ef96d9
Closes this feature request: [https://triggerdev.featurebase.app/p/isolated-dev-sessions-for-multiple-local-trigger-dev-instances](https://triggerdev.featurebase.app/p/isolated-dev-sessions-for-multiple-local-trigger-dev-instances) ### Feature notes: - CLI `trigger dev` works as before - `trigger dev --branch my-branch` to create a new branch and run against it. - `trigger dev archive --branch my-branch` to archive (or in webapp). - New webapp page to manage and archive dev branches, currently feature flagged. ### Implementation details: - No changes to data model, no backfill. `isBranchableEnvironment` column is ignored for dev branches, we use `parentEnvironmentId IS NULL` instead. - `x-trigger-branch` overloaded for preview and dev branches - New `TRIGGER_DEV_BRANCH` env var available locally. `TRIGGER_PREVIEW_BRANCH` overloaded for child runs. - Lots of new glue code to sanitise the branch checks. ### Rollout - Deploy webapp/API changes (all backwards compatible) - Manual tests on some orgs - Deploy docs, release CLI, flip feature flag for webapp feature ### NB - `api.v1.projects.$projectRef.environments.ts` will return `isBranchableEnvironment: true` for all dev environments. ### Prerequisites - [x] Typecheck will not pass until we make a new release of `@trigger.dev/platform` and bump it here
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
import { type PrismaClient } from "@trigger.dev/database";
|
|
import { prisma } from "~/db.server";
|
|
import { logger } from "./logger.server";
|
|
import { nanoid } from "nanoid";
|
|
|
|
export class ArchiveBranchService {
|
|
#prismaClient: PrismaClient;
|
|
|
|
constructor(prismaClient: PrismaClient = prisma) {
|
|
this.#prismaClient = prismaClient;
|
|
}
|
|
|
|
public async call(
|
|
// The orgFilter approach is not ideal but we need to keep it this way for now because of how the service is used in routes and api endpoints.
|
|
// Currently authorization checks are spread across the controller/route layer and the service layer. Often we check in multiple places for org/project membership.
|
|
// Ideally we would take care of both the authentication and authorization checks in the controllers and routes.
|
|
// That would unify how we handle authorization and org/project membership checks. Also it would make the service layer queries simpler.
|
|
orgFilter:
|
|
| { type: "userMembership"; userId: string }
|
|
| { type: "orgId"; organizationId: string },
|
|
{
|
|
environmentId,
|
|
}: {
|
|
environmentId: string;
|
|
}
|
|
) {
|
|
try {
|
|
const environment = await this.#prismaClient.runtimeEnvironment.findFirstOrThrow({
|
|
where: {
|
|
id: environmentId,
|
|
organization:
|
|
orgFilter.type === "userMembership"
|
|
? {
|
|
members: {
|
|
some: {
|
|
userId: orgFilter.userId,
|
|
},
|
|
},
|
|
}
|
|
: { id: orgFilter.organizationId },
|
|
// Dev branches are per-org-member, so org membership alone isn't enough:
|
|
// only the owner may archive their own dev branch. Non-dev branches (e.g.
|
|
// preview) remain scoped by org membership only.
|
|
...(orgFilter.type === "userMembership"
|
|
? {
|
|
OR: [
|
|
{ type: { not: "DEVELOPMENT" as const } },
|
|
{ orgMember: { userId: orgFilter.userId } },
|
|
],
|
|
}
|
|
: {}),
|
|
},
|
|
include: {
|
|
organization: {
|
|
select: {
|
|
id: true,
|
|
slug: true,
|
|
maximumConcurrencyLimit: true,
|
|
},
|
|
},
|
|
project: {
|
|
select: {
|
|
id: true,
|
|
slug: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// A branch is defined by having a parent; any root (dev/preview parent,
|
|
// prod, staging) has none and can't be archived. For dev, that root is
|
|
// the default branch, so give the clearer message.
|
|
if (!environment.parentEnvironmentId) {
|
|
return {
|
|
success: false as const,
|
|
error:
|
|
environment.type === "DEVELOPMENT"
|
|
? "The default development branch cannot be archived."
|
|
: "This isn't a branch, and cannot be archived.",
|
|
};
|
|
}
|
|
|
|
const slug = `${environment.slug}-${nanoid(6)}`;
|
|
const shortcode = slug;
|
|
|
|
const updatedBranch = await this.#prismaClient.runtimeEnvironment.update({
|
|
where: { id: environmentId },
|
|
data: { archivedAt: new Date(), slug, shortcode },
|
|
});
|
|
|
|
return {
|
|
success: true as const,
|
|
branch: updatedBranch,
|
|
organization: environment.organization,
|
|
project: environment.project,
|
|
};
|
|
} catch (e) {
|
|
logger.error("ArchiveBranchService error", { environmentId, error: e });
|
|
return {
|
|
success: false as const,
|
|
error: "Failed to archive branch",
|
|
};
|
|
}
|
|
}
|
|
}
|