c7861be520
Once this is merged, oxlint is at a pretty sensible baseline. **Enable `no-unused-vars`, `typescript/consistent-type-imports`, and `import/no-duplicates` lint rules** Turns on three previously-disabled oxlint rules across the monorepo and fixes all violations: - **`no-unused-vars`** – enabled as an error with standard ignore patterns: unused function arguments are ignored by default (`args: "none"`), variables/caught errors/destructured array elements prefixed with `_` are allowed, and rest siblings are permitted. - **`typescript/consistent-type-imports`** – enforced as an error; all type-only imports now use the `import type` syntax. - **`import/no-duplicates`** – enforced as an error; duplicate import statements from the same module have been merged. The remaining commits clean up the violations found across the codebase: removing unused variables/imports/type aliases, adding `_` prefixes to intentionally unused bindings, fixing duplicate imports, and converting value imports to `import type` where appropriate.
914 lines
24 KiB
TypeScript
914 lines
24 KiB
TypeScript
import type {
|
|
CreateArtifactRequestBody,
|
|
CreateBackgroundWorkerRequestBody,
|
|
DevDequeueRequestBody,
|
|
DevDisconnectRequestBody,
|
|
FailDeploymentRequestBody,
|
|
FinalizeDeploymentRequestBody,
|
|
ImportEnvironmentVariablesRequestBody,
|
|
InitializeDeploymentRequestBody,
|
|
StartDeploymentIndexingRequestBody,
|
|
TriggerTaskRequestBody,
|
|
UpsertBranchRequestBody,
|
|
WorkersCreateRequestBody,
|
|
CreateProjectRequestBody,
|
|
GetJWTRequestBody,
|
|
} from "@trigger.dev/core/v3";
|
|
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 type {
|
|
WorkloadDebugLogRequestBody,
|
|
WorkloadHeartbeatRequestBody,
|
|
WorkloadRunAttemptCompleteRequestBody,
|
|
} from "@trigger.dev/core/v3/workers";
|
|
import {
|
|
WorkloadHeartbeatResponseBody,
|
|
WorkloadRunAttemptCompleteResponseBody,
|
|
WorkloadRunAttemptStartResponseBody,
|
|
WorkloadRunLatestSnapshotResponseBody,
|
|
} from "@trigger.dev/core/v3/workers";
|
|
import type { ApiResult } from "@trigger.dev/core/v3/zodfetch";
|
|
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 {
|
|
private engineURL: string;
|
|
private source: "cli" | "mcp";
|
|
|
|
constructor(
|
|
public readonly apiURL: string,
|
|
// TODO: consider making this required
|
|
public readonly accessToken?: string,
|
|
public readonly branch?: string,
|
|
options?: { source?: "cli" | "mcp" }
|
|
) {
|
|
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: string) {
|
|
return wrapZodFetch(GetPersonalAccessTokenResponseSchema, `${this.apiURL}/api/v1/token`, {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
authorizationCode,
|
|
}),
|
|
});
|
|
}
|
|
|
|
async whoAmI(projectRef?: string) {
|
|
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: string) {
|
|
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: string, body: CreateProjectRequestBody) {
|
|
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: string, envName: string, tagName: string = "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?: { cap?: string[]; client?: string; ttlSeconds?: number }) {
|
|
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: string, envName: string, body: GetJWTRequestBody) {
|
|
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: string) {
|
|
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: string, body: CreateBackgroundWorkerRequestBody) {
|
|
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 }: { projectRef: string; env: string }) {
|
|
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: string, body: UpsertBranchRequestBody) {
|
|
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: string, env: UpsertBranchRequestBody["env"], branch: string) {
|
|
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: string) {
|
|
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: string) {
|
|
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: string,
|
|
slug: string,
|
|
params: ImportEnvironmentVariablesRequestBody
|
|
) {
|
|
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: string) {
|
|
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: CreateArtifactRequestBody) {
|
|
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: InitializeDeploymentRequestBody) {
|
|
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: string,
|
|
body: CreateBackgroundWorkerRequestBody
|
|
) {
|
|
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: string, body: FailDeploymentRequestBody) {
|
|
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: string,
|
|
body: FinalizeDeploymentRequestBody,
|
|
onLog?: (message: string) => void
|
|
): Promise<ApiResult<FailDeploymentResponseBody>> {
|
|
if (!this.accessToken) {
|
|
throw new Error("finalizeDeployment: No access token");
|
|
}
|
|
|
|
let resolvePromise: (value: ApiResult<FailDeploymentResponseBody>) => void;
|
|
|
|
const promise = new Promise<ApiResult<FailDeploymentResponseBody>>((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: string) {
|
|
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: string, body: StartDeploymentIndexingRequestBody) {
|
|
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: string) {
|
|
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?: string, signal?: AbortSignal) {
|
|
if (!this.accessToken) {
|
|
return { success: true as const, 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: string, body?: TriggerTaskRequestBody) {
|
|
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),
|
|
} as const;
|
|
}
|
|
|
|
get workers() {
|
|
return {
|
|
list: this.listWorkers.bind(this),
|
|
create: this.createWorker.bind(this),
|
|
};
|
|
}
|
|
|
|
get deployments() {
|
|
return {
|
|
unmanaged: {
|
|
latest: this.getLatestUnmanagedDeployment.bind(this),
|
|
},
|
|
};
|
|
}
|
|
|
|
private async getLatestUnmanagedDeployment() {
|
|
if (!this.accessToken) {
|
|
throw new Error("getLatestUnmanagedDeployment: No access token");
|
|
}
|
|
|
|
return wrapZodFetch(
|
|
GetLatestDeploymentResponseBody,
|
|
`${this.apiURL}/api/v1/deployments/latest`,
|
|
{
|
|
headers: this.getHeaders(),
|
|
}
|
|
);
|
|
}
|
|
|
|
private async listWorkers() {
|
|
if (!this.accessToken) {
|
|
throw new Error("listWorkers: No access token");
|
|
}
|
|
|
|
return wrapZodFetch(WorkersListResponseBody, `${this.apiURL}/api/v1/workers`, {
|
|
headers: this.getHeaders(),
|
|
});
|
|
}
|
|
|
|
private async createWorker(options: WorkersCreateRequestBody) {
|
|
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),
|
|
});
|
|
}
|
|
|
|
private async devConfig(): Promise<ApiResult<DevConfigResponseBody>> {
|
|
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(),
|
|
},
|
|
});
|
|
}
|
|
|
|
private devPresenceConnection(): EventSource {
|
|
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: any) => {
|
|
// 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;
|
|
}
|
|
|
|
private async devDisconnect(
|
|
body: DevDisconnectRequestBody
|
|
): Promise<ApiResult<DevDisconnectResponseBody>> {
|
|
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),
|
|
});
|
|
}
|
|
|
|
private async devDequeue(
|
|
body: DevDequeueRequestBody
|
|
): Promise<ApiResult<DevDequeueResponseBody>> {
|
|
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),
|
|
});
|
|
}
|
|
|
|
private async devSendDebugLog(
|
|
runId: string,
|
|
body: WorkloadDebugLogRequestBody
|
|
): Promise<ApiResult<unknown>> {
|
|
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),
|
|
});
|
|
}
|
|
|
|
private async devGetRunExecutionData(
|
|
runId: string
|
|
): Promise<ApiResult<WorkloadRunLatestSnapshotResponseBody>> {
|
|
return wrapZodFetch(
|
|
WorkloadRunLatestSnapshotResponseBody,
|
|
`${this.engineURL}/engine/v1/dev/runs/${runId}/snapshots/latest`,
|
|
{
|
|
method: "GET",
|
|
headers: {
|
|
Authorization: `Bearer ${this.accessToken}`,
|
|
Accept: "application/json",
|
|
...this.getBranchHeader(),
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
private async devHeartbeatRun(
|
|
runId: string,
|
|
snapshotId: string,
|
|
body: WorkloadHeartbeatRequestBody
|
|
): Promise<ApiResult<WorkloadHeartbeatResponseBody>> {
|
|
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),
|
|
}
|
|
);
|
|
}
|
|
|
|
private async devStartRunAttempt(
|
|
runId: string,
|
|
snapshotId: string
|
|
): Promise<ApiResult<WorkloadRunAttemptStartResponseBody>> {
|
|
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({}),
|
|
}
|
|
);
|
|
}
|
|
|
|
private async devCompleteRunAttempt(
|
|
runId: string,
|
|
snapshotId: string,
|
|
body: WorkloadRunAttemptCompleteRequestBody
|
|
): Promise<ApiResult<WorkloadRunAttemptCompleteResponseBody>> {
|
|
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),
|
|
}
|
|
);
|
|
}
|
|
|
|
private setEngineURL(engineURL: string) {
|
|
this.engineURL = engineURL.replace(/\/$/, "");
|
|
}
|
|
|
|
private getHeaders() {
|
|
return {
|
|
Authorization: `Bearer ${this.accessToken}`,
|
|
"Content-Type": "application/json",
|
|
"x-trigger-source": this.source,
|
|
...this.getBranchHeader(),
|
|
};
|
|
}
|
|
|
|
private getBranchHeader(): Record<string, string> {
|
|
return this.branch ? { "x-trigger-branch": this.branch } : {};
|
|
}
|
|
}
|