136 lines
3.6 KiB
TypeScript
136 lines
3.6 KiB
TypeScript
import { Err, FetchError, Result } from "@aigxion/error";
|
|
import { z } from "zod";
|
|
|
|
type VercelErrorResponse = {
|
|
error: string;
|
|
message: string;
|
|
};
|
|
|
|
const projectSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
});
|
|
export type Project = z.infer<typeof projectSchema>;
|
|
|
|
const environmentVariable = z.object({});
|
|
export type EnvironmentVariable = z.infer<typeof environmentVariable>;
|
|
|
|
export class Vercel {
|
|
private readonly baseUrl: string;
|
|
private readonly token: string;
|
|
private readonly teamId: string | null;
|
|
|
|
constructor(opts: { accessToken: string; baseUrl?: string; teamId?: string }) {
|
|
this.baseUrl = opts.baseUrl ?? "https://api.vercel.com";
|
|
this.token = opts.accessToken;
|
|
this.teamId = opts.teamId ?? null;
|
|
}
|
|
|
|
private async fetch<TResult>(req: {
|
|
method: "GET" | "POST" | "PUT" | "DELETE";
|
|
path: string[];
|
|
parameters?: Record<string, unknown>;
|
|
opts?: { cache?: RequestCache; revalidate?: number };
|
|
body?: unknown;
|
|
}): Promise<Result<TResult, FetchError>> {
|
|
try {
|
|
const url = new URL(req.path.join("/"), this.baseUrl);
|
|
if (req.parameters) {
|
|
for (const [key, value] of Object.entries(req.parameters)) {
|
|
if (typeof value === "undefined" || value === null) {
|
|
continue;
|
|
}
|
|
url.searchParams.set(key, value.toString());
|
|
}
|
|
}
|
|
if (this.teamId) {
|
|
url.searchParams.set("teamId", this.teamId);
|
|
}
|
|
const res = await fetch(url, {
|
|
method: req.method,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${this.token}`,
|
|
},
|
|
body: req.body ? JSON.stringify(req.body) : undefined,
|
|
cache: req.opts?.cache,
|
|
// @ts-ignore
|
|
next: {
|
|
revalidate: req.opts?.revalidate,
|
|
},
|
|
});
|
|
if (!res.ok) {
|
|
const error = (await res.json()) as VercelErrorResponse;
|
|
console.error(error);
|
|
return Err(
|
|
new FetchError(error.message, {
|
|
retry: true,
|
|
context: {
|
|
url: url.toString(),
|
|
method: req.method,
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
const body = await res.json();
|
|
// @ts-ignore
|
|
return result.success(body);
|
|
} catch (e) {
|
|
return Err(
|
|
new FetchError((e as Error).message, {
|
|
retry: true,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
public async getProject(projectId: string): Promise<Result<Project, FetchError>> {
|
|
return this.fetch({
|
|
method: "GET",
|
|
path: ["v9", "projects", projectId],
|
|
});
|
|
}
|
|
|
|
public async listProjects(): Promise<Result<Project[], FetchError>> {
|
|
const res = await this.fetch<{ projects: Project[] }>({
|
|
method: "GET",
|
|
path: ["v9", "projects"],
|
|
});
|
|
if (res.err) {
|
|
return res;
|
|
}
|
|
// @ts-ignore
|
|
return result.success(res.value.projects);
|
|
}
|
|
|
|
public async upsertEnvironmentVariable(
|
|
projectId: string,
|
|
environment: string,
|
|
key: string,
|
|
value: string,
|
|
sensitive?: boolean,
|
|
): Promise<Result<{ created: { id: string } }, FetchError>> {
|
|
return await this.fetch({
|
|
method: "POST",
|
|
path: ["v10", "projects", projectId, "env"],
|
|
parameters: { upsert: true },
|
|
body: {
|
|
key,
|
|
value,
|
|
type: sensitive ? (environment === "development" ? "encrypted" : "sensitive") : "plain",
|
|
target: [environment],
|
|
},
|
|
});
|
|
}
|
|
|
|
public async removeEnvironmentVariable(
|
|
projectId: string,
|
|
envId: string,
|
|
): Promise<Result<void, FetchError>> {
|
|
return await this.fetch({
|
|
method: "DELETE",
|
|
path: ["v10", "projects", projectId, "env", envId],
|
|
});
|
|
}
|
|
}
|