Files
Matt Aitken 068c024477 Preview branches (#2086)
* Initial preview migrations

* Modified the staging endpoint to create preview environments

* Added isBranchableEnvironment to RuntimeEnvironment

* Staging = yellow Preview = orange

* Changed the env sort order

* Set isBranchableEnvironment correctly. Create preview for new projects

* Very basic branch menu

* Creating branches from the dashboard

* Fix for string icons on project delete page

* Don’t show branch API keys

* WIP on the manage branches page

* RuntimeEnvironment added projectId index

* Only create the parentEnvironmentId column if it doesn’t exist already

* Improved the limit wording

* Add search to the branch list

* contains in both places

* Many style improvements

* Branch dropdown and v4 badge

* Arching/unarchive branches working in the dashboard

* Tidied imports

* Change preview slug from `prev` to `preview`

* Use correct color for side menu preview branch icon

* Upsert the branch and use the shortcode as a unique constraint

* Upserting working with nice messages in the dashboard

* Better errors when upserting branches

* Button shortcut, don’t allow event to propagate

* Better duplicate error message

* Filter out archived branches from the env selector

* Archiving/creating tweaked some more

* Add an archived banner to the app, fixes for archived branches and upsells

* Fixed pagination

* Disable editing schedules, pausing queues, testing tasks

* Don’t allow replaying if the env is archived

* When deploying detect the correct environment

* Get the projectClient when there’s a branch

* createGitMeta function, most code from the vercel CLI repo

* Deploy, getting the correct environment client

* Added git column to WorkerDeployment

* Add GitMeta to core schemas

* Create branch when deploying

* WIP on branch support in the API

* Delete old createTaskRunAttempt fn

* apiAuth remove export from internal functions

* Rename env var to “TRIGGER_PREVIEW_BRANCH”

* Add TRIGGER_PREVIEW_BRANCH to resolved env vars for runs

* First preview deploy and run working

* Set the preview branch in the main SDK

* Added git links to the preview branches table

* Better errors when replaying/testing archived branches

* Don’t dequeue archived environments

* Env var resolution with parent environment

* Hello world default machine small-2x to save my memory

* Fix for more env var functions

* Only return non-archived envs

* Switch to controlled state for the checkboxes

* Uncheck everything when PREVIEW is checked

* WIP on branch UI

* Show the preview branch label on the env vars list

* Fix for overriding env vars

* Adding preview branch env vars working

* Progress on new env vars

* Only allow selecting a single branch

* Layout fix when there are errors

* Set the defaultValue so there are some fields

* Conform fix for team invite page

* Archived environments don’t run scheduled tasks

* Added Git data to deployments

* Added git data to the deployment inspector

* Don’t allow upserting schedules when archived

* Deduplicate and blacklist some env vars

* Fix for wrong conform function being used

* Show a better error if all vars were blacklisted

* Added environment variable search (by key and value)

* Improved preview branch icon

* Replay now supports branches

* Schedule page render branches properly

* Show the env icon in bottom-left of the test page

* When editing older schedules (that have multi-env) show preview branches correctly

* Fix for incorrect disallowed branch name character

* Extract and improve the directory verification code

* WIP for CLI preview archive command

* Improved the preview branch action buttons

* Redirect to the project if we don’t find a matching env

* Archiving branch via the CLI working

* Fix for archiving branches

* Public access token test task

* JWTs working are with preview branches

* Add branch and git data to the Run ctx

* Updated GitMeta functions to work in CI

* Added pullRequestState

* Archive when deploying if the PR is closed/merged

* Fix for the changesets guide

* Fix for CLI dev bug introduced

* CLI promote now supports preview branches

* Add PR title. Reordered them and added tooltips

* syncEnvVars working with branches

* Added preview branch support to syncVercelEnvVars()

* Detect the branch from Vercel env var (set during build)

* Allow passing a branch in

* Use process.env.VERCEL_TOKEN as well… this used in Vercel CI

* Temp delete

* Improved regenerate api key modal

* Added Accordion component (with styles)

* Redesigned the API keys page

* Revert "Temp delete"

This reverts commit 177b92cd935a6161456bde65d01294e23ecfd47f.

* Changeset

* Fixed docs link

* The new branch panel closes when a branch is created

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

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Removed findUniques from WorkerGroupTokenService

* Made the parentEnvironmentId migrations safe

* Latest lockfile

* Update packages/cli-v3/src/commands/workers/build.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Move isValidGitBranchName to a separate file

* Move the sanitize fn too

* removeBlacklistedVariables moved to a separate file

* Moved deduplicateVariableArray to a separate file…

* Fix broken sanitizeBranchName import

* Another import fix…

* Improved blacklisted error message

* SImplified migration to use `ADD COLUMN IF NOT EXISTS "parentEnvironmentId" TEXT`

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-05-28 11:02:56 +01:00

456 lines
12 KiB
TypeScript

import type { RuntimeEnvironment, TaskRun, WorkerDeployment } from "@trigger.dev/database";
import { z } from "zod";
import { type TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters";
import type { Organization } from "~/models/organization.server";
import type { Project } from "~/models/project.server";
import { objectToSearchParams } from "./searchParams";
import { type WaitpointSearchParams } from "~/components/runs/v3/WaitpointTokenFilters";
export type OrgForPath = Pick<Organization, "slug">;
export type ProjectForPath = Pick<Project, "slug">;
export type EnvironmentForPath = Pick<RuntimeEnvironment, "slug">;
export type v3RunForPath = Pick<TaskRun, "friendlyId">;
export type v3SpanForPath = Pick<TaskRun, "spanId">;
export type DeploymentForPath = Pick<WorkerDeployment, "shortCode">;
export type TaskForPath = {
taskIdentifier: string;
};
export const OrganizationParamsSchema = z.object({
organizationSlug: z.string(),
});
export const ProjectParamSchema = OrganizationParamsSchema.extend({
projectParam: z.string(),
});
export const EnvironmentParamSchema = ProjectParamSchema.extend({
envParam: z.string(),
});
//v3
export const v3TaskParamsSchema = EnvironmentParamSchema.extend({
taskParam: z.string(),
});
export const v3RunParamsSchema = EnvironmentParamSchema.extend({
runParam: z.string(),
});
export const v3SpanParamsSchema = v3RunParamsSchema.extend({
spanParam: z.string(),
});
export const v3DeploymentParams = EnvironmentParamSchema.extend({
deploymentParam: z.string(),
});
export const v3ScheduleParams = EnvironmentParamSchema.extend({
scheduleParam: z.string(),
});
export function rootPath() {
return `/`;
}
export function accountPath() {
return `/account`;
}
export function personalAccessTokensPath() {
return `/account/tokens`;
}
export function invitesPath() {
return `/invites`;
}
export function confirmBasicDetailsPath() {
return `/confirm-basic-details`;
}
export function acceptInvitePath(token: string) {
return `/invite-accept?token=${token}`;
}
export function resendInvitePath() {
return `/invite-resend`;
}
export function logoutPath() {
return `/logout`;
}
export function revokeInvitePath() {
return `/invite-revoke`;
}
// Org
export function organizationPath(organization: OrgForPath) {
return `/orgs/${organizationParam(organization)}`;
}
export function newOrganizationPath() {
return `/orgs/new`;
}
export function selectPlanPath(organization: OrgForPath) {
return `${organizationPath(organization)}/select-plan`;
}
export function organizationTeamPath(organization: OrgForPath) {
return `${organizationPath(organization)}/settings/team`;
}
export function inviteTeamMemberPath(organization: OrgForPath) {
return `${organizationPath(organization)}/invite`;
}
export function organizationBillingPath(organization: OrgForPath) {
return `${organizationPath(organization)}/billing`;
}
export function organizationSettingsPath(organization: OrgForPath) {
return `${organizationPath(organization)}/settings`;
}
function organizationParam(organization: OrgForPath) {
return organization.slug;
}
// Project
export function newProjectPath(organization: OrgForPath, message?: string) {
return `${organizationPath(organization)}/projects/new${
message ? `?message=${encodeURIComponent(message)}` : ""
}`;
}
function projectParam(project: ProjectForPath) {
return project.slug;
}
function environmentParam(environment: EnvironmentForPath) {
return environment.slug;
}
//v3 project
export function v3ProjectPath(organization: OrgForPath, project: ProjectForPath) {
return `/orgs/${organizationParam(organization)}/projects/${projectParam(project)}`;
}
export function v3EnvironmentPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `/orgs/${organizationParam(organization)}/projects/${projectParam(
project
)}/env/${environmentParam(environment)}`;
}
export function v3TasksStreamingPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/tasks/stream`;
}
export function v3ApiKeysPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/apikeys`;
}
export function v3EnvironmentVariablesPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/environment-variables`;
}
export function v3NewEnvironmentVariablesPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentVariablesPath(organization, project, environment)}/new`;
}
export function v3ProjectAlertsPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/alerts`;
}
export function v3NewProjectAlertPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3ProjectAlertsPath(organization, project, environment)}/new`;
}
export function v3NewProjectAlertPathConnectToSlackPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3ProjectAlertsPath(organization, project, environment)}/new/connect-to-slack`;
}
export function v3TestPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/test`;
}
export function v3TestTaskPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
task: TaskForPath
) {
return `${v3TestPath(organization, project, environment)}/tasks/${encodeURIComponent(
task.taskIdentifier
)}`;
}
export function v3RunsPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
filters?: TaskRunListSearchFilters
) {
const searchParams = objectToSearchParams(filters);
const query = searchParams ? `?${searchParams.toString()}` : "";
return `${v3EnvironmentPath(organization, project, environment)}/runs${query}`;
}
export function v3RunsNextPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
filters?: TaskRunListSearchFilters
) {
const searchParams = objectToSearchParams(filters);
const query = searchParams ? `?${searchParams.toString()}` : "";
return `${v3EnvironmentPath(organization, project, environment)}/runs/next${query}`;
}
export function v3RunPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
run: v3RunForPath
) {
return `${v3RunsPath(organization, project, environment)}/${run.friendlyId}`;
}
export function v3RunRedirectPath(
organization: OrgForPath,
project: ProjectForPath,
run: v3RunForPath
) {
return `${v3ProjectPath(organization, project)}/runs/${run.friendlyId}`;
}
export function v3RunDownloadLogsPath(run: v3RunForPath) {
return `/resources/runs/${run.friendlyId}/logs/download`;
}
export function v3RunSpanPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
run: v3RunForPath,
span: v3SpanForPath
) {
return `${v3RunPath(organization, project, environment, run)}?span=${span.spanId}`;
}
export function v3RunStreamingPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
run: v3RunForPath
) {
return `${v3RunPath(organization, project, environment, run)}/stream`;
}
export function v3SchedulesPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/schedules`;
}
export function v3SchedulePath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
schedule: { friendlyId: string }
) {
return `${v3EnvironmentPath(organization, project, environment)}/schedules/${
schedule.friendlyId
}`;
}
export function v3EditSchedulePath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
schedule: { friendlyId: string }
) {
return `${v3EnvironmentPath(organization, project, environment)}/schedules/edit/${
schedule.friendlyId
}`;
}
export function v3NewSchedulePath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/schedules/new`;
}
export function v3QueuesPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/queues`;
}
export function v3WaitpointTokensPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
filters?: WaitpointSearchParams
) {
const searchParams = objectToSearchParams(filters);
const query = searchParams ? `?${searchParams.toString()}` : "";
return `${v3EnvironmentPath(organization, project, environment)}/waitpoints/tokens${query}`;
}
export function v3WaitpointTokenPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
token: { id: string },
filters?: WaitpointSearchParams
) {
const searchParams = objectToSearchParams(filters);
const query = searchParams ? `?${searchParams.toString()}` : "";
return `${v3WaitpointTokensPath(organization, project, environment)}/${token.id}${query}`;
}
export function v3BatchesPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/batches`;
}
export function v3BatchPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
batch: { friendlyId: string }
) {
return `${v3EnvironmentPath(organization, project, environment)}/batches?id=${batch.friendlyId}`;
}
export function v3BatchRunsPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
batch: { friendlyId: string }
) {
return `${v3RunsPath(organization, project, environment, { batchId: batch.friendlyId })}`;
}
export function v3ProjectSettingsPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/settings`;
}
export function v3DeploymentsPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/deployments`;
}
export function v3DeploymentPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
deployment: DeploymentForPath,
currentPage: number
) {
const query = currentPage ? `?page=${currentPage}` : "";
return `${v3DeploymentsPath(organization, project, environment)}/${deployment.shortCode}${query}`;
}
export function v3DeploymentVersionPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
version: string
) {
return `${v3DeploymentsPath(organization, project, environment)}?version=${version}`;
}
export function branchesPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath
) {
return `${v3EnvironmentPath(organization, project, environment)}/branches`;
}
export function v3BillingPath(organization: OrgForPath, message?: string) {
return `${organizationPath(organization)}/settings/billing${
message ? `?message=${encodeURIComponent(message)}` : ""
}`;
}
export function v3StripePortalPath(organization: OrgForPath) {
return `/resources/${organization.slug}/subscription/portal`;
}
export function v3UsagePath(organization: OrgForPath) {
return `${organizationPath(organization)}/settings/usage`;
}
// Docs
export function docsRoot() {
return "https://trigger.dev/docs";
}
export function docsPath(path: string) {
return `${docsRoot()}/${path}`;
}
export function docsTroubleshootingPath(path: string) {
return `${docsRoot()}/v3/troubleshooting`;
}
export function adminPath() {
return `/@`;
}