trigger.dev
Version:
A Command-Line Interface for Trigger.dev projects
535 lines • 21.4 kB
JavaScript
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