UNPKG

trigger.dev

Version:

A Command-Line Interface for Trigger.dev projects

535 lines • 21.4 kB
import { CreateAuthorizationCodeResponseSchema, CreateArtifactResponseBody, CreateBackgroundWorkerResponse, DevConfigResponseBody, DevDequeueResponseBody, 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"; export class CliApiClient { apiURL; accessToken; branch; engineURL; constructor(apiURL, // TODO: consider making this required accessToken, branch) { this.apiURL = apiURL; this.accessToken = accessToken; this.branch = branch; this.apiURL = apiURL.replace(/\/$/, ""); this.engineURL = this.apiURL; this.branch = branch; } 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 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, 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({ 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 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), 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", }, }); } 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}`, }, }), }); 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 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", }, 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", }, 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", }, }); } 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", }, 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", }, //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", }, body: JSON.stringify(body), }); } setEngineURL(engineURL) { this.engineURL = engineURL.replace(/\/$/, ""); } getHeaders() { const headers = { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }; if (this.branch) { headers["x-trigger-branch"] = this.branch; } return headers; } } //# sourceMappingURL=apiClient.js.map