UNPKG

trigger.dev

Version:

A Command-Line Interface for Trigger.dev projects

616 lines • 24.4 kB
import { CreateAuthorizationCodeResponseSchema, CreateArtifactResponseBody, CreateBackgroundWorkerResponse, DevConfigResponseBody, DevDequeueResponseBody, DevDisconnectResponseBody, EnvironmentVariableResponseBody, FailDeploymentResponseBody, GetDeploymentResponseBody, GetEnvironmentVariablesResponseBody, GetLatestDeploymentResponseBody, GetPersonalAccessTokenResponseSchema, GetProjectEnvResponse, GetProjectResponseBody, GetProjectsResponseBody, InitializeDeploymentResponseBody, PromoteDeploymentResponseBody, StartDeploymentIndexingResponseBody, TriggerTaskResponse, UpsertBranchResponseBody, WhoAmIResponseSchema, WorkersCreateResponseBody, WorkersListResponseBody, GetOrgsResponseBody, GetWorkerByTagResponse, GetJWTResponse, ApiBranchListResponseBody, GenerateRegistryCredentialsResponseBody, RemoteBuildProviderStatusResponseBody, } from "@trigger.dev/core/v3"; import { WorkloadHeartbeatResponseBody, WorkloadRunAttemptCompleteResponseBody, WorkloadRunAttemptStartResponseBody, WorkloadRunLatestSnapshotResponseBody, } from "@trigger.dev/core/v3/workers"; import { wrapZodFetch, zodfetchSSE } from "@trigger.dev/core/v3/zodfetch"; import { EventSource } from "eventsource"; import { z } from "zod"; import { logger } from "./utilities/logger.js"; import { VERSION } from "./version.js"; const MintUserActorTokenResponseSchema = z.object({ token: z.string(), expiresInSeconds: z.number(), }); const CliPlatformNotificationResponseSchema = z.object({ notification: z .object({ id: z.string(), payload: z.object({ version: z.string(), data: z.object({ type: z.enum(["info", "warn", "error", "success"]), title: z.string(), description: z.string(), actionLabel: z.string().optional(), actionUrl: z.string().optional(), discovery: z .object({ filePatterns: z.array(z.string()), contentPattern: z.string().optional(), matchBehavior: z.enum(["show-if-found", "show-if-not-found"]), }) .optional(), }), }), showCount: z.number(), firstSeenAt: z.string(), }) .nullable(), }); export class CliApiClient { apiURL; accessToken; branch; engineURL; source; constructor(apiURL, // TODO: consider making this required accessToken, branch, options) { this.apiURL = apiURL; this.accessToken = accessToken; this.branch = branch; this.apiURL = apiURL.replace(/\/$/, ""); this.engineURL = this.apiURL; this.source = options?.source ?? "cli"; } async createAuthorizationCode() { return wrapZodFetch(CreateAuthorizationCodeResponseSchema, `${this.apiURL}/api/v1/authorization-code`, { method: "POST", }); } async getPersonalAccessToken(authorizationCode) { return wrapZodFetch(GetPersonalAccessTokenResponseSchema, `${this.apiURL}/api/v1/token`, { method: "POST", body: JSON.stringify({ authorizationCode, }), }); } async whoAmI(projectRef) { if (!this.accessToken) { throw new Error("whoAmI: No access token"); } const url = new URL("/api/v2/whoami", this.apiURL); if (projectRef) { url.searchParams.append("projectRef", projectRef); } return wrapZodFetch(WhoAmIResponseSchema, url.href, { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }, }); } async retrieveExternals() { return wrapZodFetch(z.object({ externals: z.array(z.string()) }), `https://jsonhero.io/j/GU7CwoDOL40k.json`, { headers: { "Content-Type": "application/json", }, }); } async getProject(projectRef) { if (!this.accessToken) { throw new Error("getProject: No access token"); } return wrapZodFetch(GetProjectResponseBody, `${this.apiURL}/api/v1/projects/${projectRef}`, { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }, }); } async getProjects() { if (!this.accessToken) { throw new Error("getProjects: No access token"); } return wrapZodFetch(GetProjectsResponseBody, `${this.apiURL}/api/v1/projects`, { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }, }); } async getOrgs() { if (!this.accessToken) { throw new Error("getOrgs: No access token"); } return wrapZodFetch(GetOrgsResponseBody, `${this.apiURL}/api/v1/orgs`, { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }, }); } async createProject(orgParam, body) { if (!this.accessToken) { throw new Error("createProject: No access token"); } return wrapZodFetch(GetProjectResponseBody, `${this.apiURL}/api/v1/orgs/${orgParam}/projects`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body), }); } async getWorkerByTag(projectRef, envName, tagName = "current") { if (!this.accessToken) { throw new Error("getWorkerByTag: No access token"); } return wrapZodFetch(GetWorkerByTagResponse, `${this.apiURL}/api/v1/projects/${projectRef}/${envName}/workers/${tagName}`, { headers: this.getHeaders(), }); } async mintUserActorToken(body) { if (!this.accessToken) { throw new Error("mintUserActorToken: No access token"); } return wrapZodFetch(MintUserActorTokenResponseSchema, `${this.apiURL}/api/v1/auth/user-actor-token`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body ?? {}), }); } async getJWT(projectRef, envName, body) { if (!this.accessToken) { throw new Error("getJWT: No access token"); } return wrapZodFetch(GetJWTResponse, `${this.apiURL}/api/v1/projects/${projectRef}/${envName}/jwt`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body), }); } async getDevStatus(projectRef) { if (!this.accessToken) { throw new Error("getDevStatus: No access token"); } return wrapZodFetch(z.object({ isConnected: z.boolean() }), `${this.apiURL}/api/v1/projects/${projectRef}/dev-status`, { headers: this.getHeaders(), }); } async createBackgroundWorker(projectRef, body) { if (!this.accessToken) { throw new Error("createBackgroundWorker: No access token"); } return wrapZodFetch(CreateBackgroundWorkerResponse, `${this.apiURL}/api/v1/projects/${projectRef}/background-workers`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body), }); } async getProjectEnv({ projectRef, env }) { if (!this.accessToken) { throw new Error("getProjectDevEnv: No access token"); } return wrapZodFetch(GetProjectEnvResponse, `${this.apiURL}/api/v1/projects/${projectRef}/${env}`, { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }, }); } async upsertBranch(projectRef, body) { if (!this.accessToken) { throw new Error("upsertBranch: No access token"); } return wrapZodFetch(UpsertBranchResponseBody, `${this.apiURL}/api/v1/projects/${projectRef}/branches`, { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify(body), }); } async archiveBranch(projectRef, env, branch) { if (!this.accessToken) { throw new Error("archiveBranch: No access token"); } return wrapZodFetch(z.object({ branch: z.object({ id: z.string() }) }), `${this.apiURL}/api/v1/projects/${projectRef}/branches/archive`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify({ env, branch }), }); } async listBranches(projectRef) { if (!this.accessToken) { throw new Error("listBranches: No access token"); } return wrapZodFetch(ApiBranchListResponseBody, `${this.apiURL}/api/v1/projects/${projectRef}/branches`, { headers: this.getHeaders(), }); } async getEnvironmentVariables(projectRef) { if (!this.accessToken) { throw new Error("getEnvironmentVariables: No access token"); } return wrapZodFetch(GetEnvironmentVariablesResponseBody, `${this.apiURL}/api/v1/projects/${projectRef}/envvars`, { headers: this.getHeaders(), }); } async importEnvVars(projectRef, slug, params) { if (!this.accessToken) { throw new Error("importEnvVars: No access token"); } return wrapZodFetch(EnvironmentVariableResponseBody, `${this.apiURL}/api/v1/projects/${projectRef}/envvars/${slug}/import`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(params), }); } async getRemoteBuildProviderStatus() { return wrapZodFetch(RemoteBuildProviderStatusResponseBody, `${this.apiURL}/api/v1/remote-build-provider-status`, { method: "GET", headers: { ...this.getHeaders(), // probably a good idea to add this to the other requests too "x-trigger-cli-version": VERSION, }, }); } async generateRegistryCredentials(deploymentId) { if (!this.accessToken) { throw new Error("generateRegistryCredentials: No access token"); } return wrapZodFetch(GenerateRegistryCredentialsResponseBody, `${this.apiURL}/api/v1/deployments/${deploymentId}/generate-registry-credentials`, { method: "POST", headers: this.getHeaders(), body: "{}", }); } async createArtifact(body) { if (!this.accessToken) { throw new Error("createArtifact: No access token"); } return wrapZodFetch(CreateArtifactResponseBody, `${this.apiURL}/api/v1/artifacts`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body), }); } async initializeDeployment(body) { if (!this.accessToken) { throw new Error("initializeDeployment: No access token"); } return wrapZodFetch(InitializeDeploymentResponseBody, `${this.apiURL}/api/v1/deployments`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body), }); } async createDeploymentBackgroundWorker(deploymentId, body) { if (!this.accessToken) { throw new Error("createDeploymentBackgroundWorker: No access token"); } return wrapZodFetch(CreateBackgroundWorkerResponse, `${this.apiURL}/api/v1/deployments/${deploymentId}/background-workers`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body), }); } async failDeployment(id, body) { if (!this.accessToken) { throw new Error("failDeployment: No access token"); } return wrapZodFetch(FailDeploymentResponseBody, `${this.apiURL}/api/v1/deployments/${id}/fail`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body), }); } async finalizeDeployment(id, body, onLog) { if (!this.accessToken) { throw new Error("finalizeDeployment: No access token"); } let resolvePromise; const promise = new Promise((resolve) => { resolvePromise = resolve; }); const source = zodfetchSSE({ url: `${this.apiURL}/api/v3/deployments/${id}/finalize`, request: { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body), }, messages: { error: z.object({ error: z.string() }), log: z.object({ message: z.string() }), complete: FailDeploymentResponseBody, }, }); source.onConnectionError((error) => { let message = error.message ?? "Unknown error"; if (error.status !== undefined) { message = `HTTP ${error.status} ${message}`; } resolvePromise({ success: false, error: message, }); }); source.onMessage("complete", (message) => { resolvePromise({ success: true, data: message, }); }); source.onMessage("error", ({ error }) => { resolvePromise({ success: false, error, }); }); if (onLog) { source.onMessage("log", ({ message }) => { onLog(message); }); } const result = await promise; source.stop(); return result; } async promoteDeployment(version) { if (!this.accessToken) { throw new Error("promoteDeployment: No access token"); } return wrapZodFetch(PromoteDeploymentResponseBody, `${this.apiURL}/api/v1/deployments/${version}/promote`, { method: "POST", headers: this.getHeaders(), }); } async startDeploymentIndexing(deploymentId, body) { if (!this.accessToken) { throw new Error("startDeploymentIndexing: No access token"); } return wrapZodFetch(StartDeploymentIndexingResponseBody, `${this.apiURL}/api/v1/deployments/${deploymentId}/start-indexing`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body), }); } async getDeployment(deploymentId) { if (!this.accessToken) { throw new Error("getDeployment: No access token"); } return wrapZodFetch(GetDeploymentResponseBody, `${this.apiURL}/api/v1/deployments/${deploymentId}`, { headers: this.getHeaders(), }); } async getCliPlatformNotification(projectRef, signal) { if (!this.accessToken) { return { success: true, data: { notification: null } }; } const url = new URL("/api/v1/platform-notifications", this.apiURL); if (projectRef) { url.searchParams.set("projectRef", projectRef); } return wrapZodFetch(CliPlatformNotificationResponseSchema, url.href, { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }, signal, }); } async triggerTaskRun(taskId, body) { if (!this.accessToken) { throw new Error("triggerTaskRun: No access token"); } return wrapZodFetch(TriggerTaskResponse, `${this.apiURL}/api/v1/tasks/${taskId}/trigger`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(body ?? {}), }); } get dev() { return { config: this.devConfig.bind(this), presenceConnection: this.devPresenceConnection.bind(this), dequeue: this.devDequeue.bind(this), sendDebugLog: this.devSendDebugLog.bind(this), getRunExecutionData: this.devGetRunExecutionData.bind(this), heartbeatRun: this.devHeartbeatRun.bind(this), startRunAttempt: this.devStartRunAttempt.bind(this), completeRunAttempt: this.devCompleteRunAttempt.bind(this), disconnect: this.devDisconnect.bind(this), setEngineURL: this.setEngineURL.bind(this), }; } get workers() { return { list: this.listWorkers.bind(this), create: this.createWorker.bind(this), }; } get deployments() { return { unmanaged: { latest: this.getLatestUnmanagedDeployment.bind(this), }, }; } async getLatestUnmanagedDeployment() { if (!this.accessToken) { throw new Error("getLatestUnmanagedDeployment: No access token"); } return wrapZodFetch(GetLatestDeploymentResponseBody, `${this.apiURL}/api/v1/deployments/latest`, { headers: this.getHeaders(), }); } async listWorkers() { if (!this.accessToken) { throw new Error("listWorkers: No access token"); } return wrapZodFetch(WorkersListResponseBody, `${this.apiURL}/api/v1/workers`, { headers: this.getHeaders(), }); } async createWorker(options) { if (!this.accessToken) { throw new Error("createWorker: No access token"); } return wrapZodFetch(WorkersCreateResponseBody, `${this.apiURL}/api/v1/workers`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify(options), }); } async devConfig() { if (!this.accessToken) { throw new Error("devConfig: No access token"); } return wrapZodFetch(DevConfigResponseBody, `${this.engineURL}/engine/v1/dev/config`, { headers: { Authorization: `Bearer ${this.accessToken}`, Accept: "application/json", ...this.getBranchHeader(), }, }); } devPresenceConnection() { if (!this.accessToken) { throw new Error("connectToPresence: No access token"); } let retryCount = 0; const maxRetries = 5; const retryDelay = 1000; // Start with 1 second delay const eventSource = new EventSource(`${this.engineURL}/engine/v1/dev/presence`, { fetch: (input, init) => fetch(input, { ...init, headers: { ...init?.headers, Authorization: `Bearer ${this.accessToken}`, ...this.getBranchHeader(), }, }), }); eventSource.onopen = () => { logger.debug("Presence connection established"); retryCount = 0; // Reset retry count on successful connection }; eventSource.onerror = (error) => { // The connection will automatically try to reconnect logger.debug("Presence connection error, will automatically attempt to reconnect", { error, readyState: eventSource.readyState, }); if (eventSource.readyState === EventSource.CLOSED) { logger.debug("Presence connection permanently closed", { error, retryCount }); if (retryCount < maxRetries) { retryCount++; const backoffDelay = retryDelay * Math.pow(2, retryCount - 1); // Exponential backoff logger.debug(`Attempting reconnection in ${backoffDelay}ms (attempt ${retryCount}/${maxRetries})`); eventSource.close(); setTimeout(() => { this.devPresenceConnection(); }, backoffDelay); } else { logger.debug("Max retry attempts reached, giving up"); } } }; return eventSource; } async devDisconnect(body) { if (!this.accessToken) { throw new Error("devDisconnect: No access token"); } return wrapZodFetch(DevDisconnectResponseBody, `${this.engineURL}/engine/v1/dev/disconnect`, { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, Accept: "application/json", ...this.getBranchHeader(), }, body: JSON.stringify(body), }); } async devDequeue(body) { if (!this.accessToken) { throw new Error("devConfig: No access token"); } return wrapZodFetch(DevDequeueResponseBody, `${this.engineURL}/engine/v1/dev/dequeue`, { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, Accept: "application/json", ...this.getBranchHeader(), }, body: JSON.stringify(body), }); } async devSendDebugLog(runId, body) { if (!this.accessToken) { throw new Error("devConfig: No access token"); } return wrapZodFetch(z.unknown(), `${this.engineURL}/engine/v1/dev/runs/${runId}/logs/debug`, { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, Accept: "application/json", "Content-Type": "application/json", ...this.getBranchHeader(), }, body: JSON.stringify(body), }); } async devGetRunExecutionData(runId) { return wrapZodFetch(WorkloadRunLatestSnapshotResponseBody, `${this.engineURL}/engine/v1/dev/runs/${runId}/snapshots/latest`, { method: "GET", headers: { Authorization: `Bearer ${this.accessToken}`, Accept: "application/json", ...this.getBranchHeader(), }, }); } async devHeartbeatRun(runId, snapshotId, body) { return wrapZodFetch(WorkloadHeartbeatResponseBody, `${this.engineURL}/engine/v1/dev/runs/${runId}/snapshots/${snapshotId}/heartbeat`, { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, Accept: "application/json", "Content-Type": "application/json", ...this.getBranchHeader(), }, body: JSON.stringify(body), }); } async devStartRunAttempt(runId, snapshotId) { return wrapZodFetch(WorkloadRunAttemptStartResponseBody, `${this.engineURL}/engine/v1/dev/runs/${runId}/snapshots/${snapshotId}/attempts/start`, { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, Accept: "application/json", ...this.getBranchHeader(), }, //no body at the moment, but we'll probably add things soon body: JSON.stringify({}), }); } async devCompleteRunAttempt(runId, snapshotId, body) { return wrapZodFetch(WorkloadRunAttemptCompleteResponseBody, `${this.engineURL}/engine/v1/dev/runs/${runId}/snapshots/${snapshotId}/attempts/complete`, { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, Accept: "application/json", ...this.getBranchHeader(), }, body: JSON.stringify(body), }); } setEngineURL(engineURL) { this.engineURL = engineURL.replace(/\/$/, ""); } getHeaders() { return { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", "x-trigger-source": this.source, ...this.getBranchHeader(), }; } getBranchHeader() { return this.branch ? { "x-trigger-branch": this.branch } : {}; } } //# sourceMappingURL=apiClient.js.map