mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 15:41:27 +00:00
Merge branch 'main' into feat/terraformCloudIntegration
This commit is contained in:
@@ -7,6 +7,7 @@ export enum AppConnection {
|
||||
AzureAppConfiguration = "azure-app-configuration",
|
||||
Humanitec = "humanitec",
|
||||
TerraformCloud = "terraform-cloud",
|
||||
Vercel = "vercel",
|
||||
Postgres = "postgres",
|
||||
MsSql = "mssql"
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ import {
|
||||
TerraformCloudConnectionMethod,
|
||||
validateTerraformCloudConnectionCredentials
|
||||
} from "./terraform-cloud";
|
||||
import { VercelConnectionMethod } from "./vercel";
|
||||
import { getVercelConnectionListItem, validateVercelConnectionCredentials } from "./vercel/vercel-connection-fns";
|
||||
|
||||
export const listAppConnectionOptions = () => {
|
||||
return [
|
||||
@@ -57,6 +59,7 @@ export const listAppConnectionOptions = () => {
|
||||
getDatabricksConnectionListItem(),
|
||||
getHumanitecConnectionListItem(),
|
||||
getTerraformCloudConnectionListItem(),
|
||||
getVercelConnectionListItem(),
|
||||
getPostgresConnectionListItem(),
|
||||
getMsSqlConnectionListItem()
|
||||
].sort((a, b) => a.name.localeCompare(b.name));
|
||||
@@ -115,7 +118,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TAppConnect
|
||||
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator
|
||||
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator
|
||||
};
|
||||
|
||||
export const validateAppConnectionCredentials = async (
|
||||
@@ -140,6 +144,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
||||
return "Service Principal";
|
||||
case HumanitecConnectionMethod.ApiToken:
|
||||
case TerraformCloudConnectionMethod.ApiToken:
|
||||
case VercelConnectionMethod.ApiToken:
|
||||
return "API Token";
|
||||
case PostgresConnectionMethod.UsernameAndPassword:
|
||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||
@@ -184,5 +189,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
||||
[AppConnection.Humanitec]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported
|
||||
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Vercel]: platformManagedCredentialsNotSupported
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
||||
[AppConnection.Databricks]: "Databricks",
|
||||
[AppConnection.Humanitec]: "Humanitec",
|
||||
[AppConnection.TerraformCloud]: "Terraform Cloud",
|
||||
[AppConnection.Vercel]: "Vercel",
|
||||
[AppConnection.Postgres]: "PostgreSQL",
|
||||
[AppConnection.MsSql]: "Microsoft SQL Server"
|
||||
};
|
||||
|
||||
@@ -43,6 +43,8 @@ import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
||||
import { ValidateTerraformCloudConnectionCredentialsSchema } from "./terraform-cloud";
|
||||
import { terraformCloudConnectionService } from "./terraform-cloud/terraform-cloud-connection-service";
|
||||
import { ValidateVercelConnectionCredentialsSchema } from "./vercel";
|
||||
import { vercelConnectionService } from "./vercel/vercel-connection-service";
|
||||
|
||||
export type TAppConnectionServiceFactoryDep = {
|
||||
appConnectionDAL: TAppConnectionDALFactory;
|
||||
@@ -61,6 +63,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
||||
[AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema,
|
||||
[AppConnection.Humanitec]: ValidateHumanitecConnectionCredentialsSchema,
|
||||
[AppConnection.TerraformCloud]: ValidateTerraformCloudConnectionCredentialsSchema,
|
||||
[AppConnection.Vercel]: ValidateVercelConnectionCredentialsSchema,
|
||||
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
|
||||
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema
|
||||
};
|
||||
@@ -434,6 +437,7 @@ export const appConnectionServiceFactory = ({
|
||||
databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||
aws: awsConnectionService(connectAppConnectionById),
|
||||
humanitec: humanitecConnectionService(connectAppConnectionById),
|
||||
terraformCloud: terraformCloudConnectionService(connectAppConnectionById)
|
||||
terraformCloud: terraformCloudConnectionService(connectAppConnectionById),
|
||||
vercel: vercelConnectionService(connectAppConnectionById)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -57,6 +57,12 @@ import {
|
||||
TTerraformCloudConnectionInput,
|
||||
TValidateTerraformCloudConnectionCredentialsSchema
|
||||
} from "./terraform-cloud";
|
||||
import {
|
||||
TValidateVercelConnectionCredentialsSchema,
|
||||
TVercelConnection,
|
||||
TVercelConnectionConfig,
|
||||
TVercelConnectionInput
|
||||
} from "./vercel";
|
||||
|
||||
export type TAppConnection = { id: string } & (
|
||||
| TAwsConnection
|
||||
@@ -67,6 +73,7 @@ export type TAppConnection = { id: string } & (
|
||||
| TDatabricksConnection
|
||||
| THumanitecConnection
|
||||
| TTerraformCloudConnection
|
||||
| TVercelConnection
|
||||
| TPostgresConnection
|
||||
| TMsSqlConnection
|
||||
);
|
||||
@@ -84,6 +91,7 @@ export type TAppConnectionInput = { id: string } & (
|
||||
| TDatabricksConnectionInput
|
||||
| THumanitecConnectionInput
|
||||
| TTerraformCloudConnectionInput
|
||||
| TVercelConnectionInput
|
||||
| TPostgresConnectionInput
|
||||
| TMsSqlConnectionInput
|
||||
);
|
||||
@@ -108,6 +116,7 @@ export type TAppConnectionConfig =
|
||||
| TDatabricksConnectionConfig
|
||||
| THumanitecConnectionConfig
|
||||
| TTerraformCloudConnectionConfig
|
||||
| TVercelConnectionConfig
|
||||
| TSqlConnectionConfig;
|
||||
|
||||
export type TValidateAppConnectionCredentialsSchema =
|
||||
@@ -120,7 +129,8 @@ export type TValidateAppConnectionCredentialsSchema =
|
||||
| TValidateHumanitecConnectionCredentialsSchema
|
||||
| TValidatePostgresConnectionCredentialsSchema
|
||||
| TValidateMsSqlConnectionCredentialsSchema
|
||||
| TValidateTerraformCloudConnectionCredentialsSchema;
|
||||
| TValidateTerraformCloudConnectionCredentialsSchema
|
||||
| TValidateVercelConnectionCredentialsSchema;
|
||||
|
||||
export type TListAwsConnectionKmsKeys = {
|
||||
connectionId: string;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./vercel-connection-enums";
|
||||
export * from "./vercel-connection-fns";
|
||||
export * from "./vercel-connection-schemas";
|
||||
export * from "./vercel-connection-types";
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum VercelConnectionMethod {
|
||||
ApiToken = "api-token"
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { TVercelBranches } from "@app/services/integration-auth/integration-auth-types";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
|
||||
import { VercelConnectionMethod } from "./vercel-connection-enums";
|
||||
import {
|
||||
TVercelConnection,
|
||||
TVercelConnectionConfig,
|
||||
VercelApp,
|
||||
VercelEnvironment,
|
||||
VercelOrgWithApps
|
||||
} from "./vercel-connection-types";
|
||||
|
||||
export const getVercelConnectionListItem = () => {
|
||||
return {
|
||||
name: "Vercel" as const,
|
||||
app: AppConnection.Vercel as const,
|
||||
methods: Object.values(VercelConnectionMethod) as [VercelConnectionMethod.ApiToken]
|
||||
};
|
||||
};
|
||||
|
||||
export const validateVercelConnectionCredentials = async (config: TVercelConnectionConfig) => {
|
||||
const { credentials: inputCredentials } = config;
|
||||
|
||||
let response: AxiosResponse<VercelApp[]> | null = null;
|
||||
|
||||
try {
|
||||
response = await request.get<VercelApp[]>(`${IntegrationUrls.VERCEL_API_URL}/v9/projects`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${inputCredentials.apiToken}`
|
||||
}
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw new BadRequestError({
|
||||
message: "Unable to validate connection - verify credentials"
|
||||
});
|
||||
}
|
||||
|
||||
if (!response?.data) {
|
||||
throw new InternalServerError({
|
||||
message: "Failed to get organizations: Response was empty"
|
||||
});
|
||||
}
|
||||
|
||||
return inputCredentials;
|
||||
};
|
||||
|
||||
interface ApiResponse<T> {
|
||||
pagination?: {
|
||||
count: number;
|
||||
next: number;
|
||||
};
|
||||
data: T[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
async function fetchAllPages<T>(
|
||||
apiUrl: string,
|
||||
apiToken: string,
|
||||
initialParams: Record<string, string | number> = {},
|
||||
dataPath?: string
|
||||
): Promise<T[]> {
|
||||
const allItems: T[] = [];
|
||||
let hasMoreItems = true;
|
||||
let params: Record<string, string | number> = { ...initialParams, limit: 100 };
|
||||
|
||||
while (hasMoreItems) {
|
||||
try {
|
||||
const response = await request.get<ApiResponse<T>>(apiUrl, {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
if (!response?.data) {
|
||||
throw new InternalServerError({
|
||||
message: `Failed to fetch data from ${apiUrl}: Response was empty or malformed`
|
||||
});
|
||||
}
|
||||
|
||||
let itemsData: T[];
|
||||
|
||||
if (dataPath && dataPath in response.data) {
|
||||
itemsData = response.data[dataPath] as T[];
|
||||
} else {
|
||||
itemsData = response.data.data;
|
||||
}
|
||||
|
||||
if (!Array.isArray(itemsData)) {
|
||||
throw new InternalServerError({
|
||||
message: `Failed to fetch data from ${apiUrl}: Expected array but got ${typeof itemsData}`
|
||||
});
|
||||
}
|
||||
|
||||
allItems.push(...itemsData);
|
||||
|
||||
if (response.data.pagination?.next) {
|
||||
params = { ...params, since: response.data.pagination.next };
|
||||
} else {
|
||||
hasMoreItems = false;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to fetch data from ${apiUrl}: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return allItems;
|
||||
}
|
||||
|
||||
async function fetchOrgProjects(orgId: string, apiToken: string): Promise<VercelApp[]> {
|
||||
return fetchAllPages<VercelApp>(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects`,
|
||||
apiToken,
|
||||
{ teamId: orgId },
|
||||
"projects"
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchProjectEnvironments(
|
||||
projectId: string,
|
||||
teamId: string,
|
||||
apiToken: string
|
||||
): Promise<VercelEnvironment[]> {
|
||||
try {
|
||||
return await fetchAllPages<VercelEnvironment>(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${projectId}/custom-environments?teamId=${teamId}`,
|
||||
apiToken,
|
||||
{},
|
||||
"environments"
|
||||
);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPreviewBranches(projectId: string, apiToken: string): Promise<string[]> {
|
||||
try {
|
||||
const { data } = await request.get<TVercelBranches[]>(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v1/integrations/git-branches`,
|
||||
{
|
||||
params: {
|
||||
projectId
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
return data.filter((b) => b.ref !== "main").map((b) => b.ref);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
type VercelTeam = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
type VercelUserResponse = {
|
||||
user: {
|
||||
id: string;
|
||||
name: string;
|
||||
username: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const listProjects = async (appConnection: TVercelConnection): Promise<VercelOrgWithApps[]> => {
|
||||
const { credentials } = appConnection;
|
||||
const { apiToken } = credentials;
|
||||
|
||||
const orgs = await fetchAllPages<VercelTeam>(`${IntegrationUrls.VERCEL_API_URL}/v2/teams`, apiToken, {}, "teams");
|
||||
|
||||
const personalAccountResponse = await request.get<VercelUserResponse>(`${IntegrationUrls.VERCEL_API_URL}/v2/user`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
if (personalAccountResponse?.data?.user) {
|
||||
const { user } = personalAccountResponse.data;
|
||||
orgs.push({
|
||||
id: user.id,
|
||||
name: user.name || "Personal Account",
|
||||
slug: user.username || "personal"
|
||||
});
|
||||
}
|
||||
|
||||
const orgsWithApps: VercelOrgWithApps[] = [];
|
||||
|
||||
const orgPromises = orgs.map(async (org) => {
|
||||
try {
|
||||
const projects = await fetchOrgProjects(org.id, apiToken);
|
||||
|
||||
const enhancedProjectsPromises = projects.map(async (project) => {
|
||||
try {
|
||||
const [environments, previewBranches] = await Promise.all([
|
||||
fetchProjectEnvironments(project.name, org.id, apiToken),
|
||||
fetchPreviewBranches(project.id, apiToken)
|
||||
]);
|
||||
|
||||
return {
|
||||
name: project.name,
|
||||
id: project.id,
|
||||
envs: environments,
|
||||
previewBranches
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
name: project.name,
|
||||
id: project.id,
|
||||
envs: [],
|
||||
previewBranches: []
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const enhancedProjects = await Promise.all(enhancedProjectsPromises);
|
||||
|
||||
return {
|
||||
...org,
|
||||
apps: enhancedProjects
|
||||
};
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(orgPromises);
|
||||
|
||||
results.forEach((result) => {
|
||||
if (result !== null) {
|
||||
orgsWithApps.push(result);
|
||||
}
|
||||
});
|
||||
|
||||
return orgsWithApps;
|
||||
};
|
||||
|
||||
export const getProjectEnvironmentVariables = (project: VercelApp): Record<string, string> => {
|
||||
const envVars: Record<string, string> = {};
|
||||
|
||||
if (!project.envs) return envVars;
|
||||
|
||||
project.envs.forEach((env) => {
|
||||
if (env.slug && env.type !== "gitBranch") {
|
||||
const { id, slug } = env;
|
||||
envVars[id] = slug;
|
||||
}
|
||||
});
|
||||
|
||||
return envVars;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import z from "zod";
|
||||
|
||||
import { AppConnections } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
BaseAppConnectionSchema,
|
||||
GenericCreateAppConnectionFieldsSchema,
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { VercelConnectionMethod } from "./vercel-connection-enums";
|
||||
|
||||
export const VercelConnectionAccessTokenCredentialsSchema = z.object({
|
||||
apiToken: z.string().trim().min(1, "API Token required").describe(AppConnections.CREDENTIALS.VERCEL.apiToken)
|
||||
});
|
||||
|
||||
const BaseVercelConnectionSchema = BaseAppConnectionSchema.extend({
|
||||
app: z.literal(AppConnection.Vercel)
|
||||
});
|
||||
|
||||
export const VercelConnectionSchema = BaseVercelConnectionSchema.extend({
|
||||
method: z.literal(VercelConnectionMethod.ApiToken),
|
||||
credentials: VercelConnectionAccessTokenCredentialsSchema
|
||||
});
|
||||
|
||||
export const SanitizedVercelConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseVercelConnectionSchema.extend({
|
||||
method: z.literal(VercelConnectionMethod.ApiToken),
|
||||
credentials: VercelConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateVercelConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z.literal(VercelConnectionMethod.ApiToken).describe(AppConnections.CREATE(AppConnection.Vercel).method),
|
||||
credentials: VercelConnectionAccessTokenCredentialsSchema.describe(
|
||||
AppConnections.CREATE(AppConnection.Vercel).credentials
|
||||
)
|
||||
})
|
||||
]);
|
||||
|
||||
export const CreateVercelConnectionSchema = ValidateVercelConnectionCredentialsSchema.and(
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.Vercel)
|
||||
);
|
||||
|
||||
export const UpdateVercelConnectionSchema = z
|
||||
.object({
|
||||
credentials: VercelConnectionAccessTokenCredentialsSchema.optional().describe(
|
||||
AppConnections.UPDATE(AppConnection.Vercel).credentials
|
||||
)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Vercel));
|
||||
|
||||
export const VercelConnectionListItemSchema = z.object({
|
||||
name: z.literal("Vercel"),
|
||||
app: z.literal(AppConnection.Vercel),
|
||||
methods: z.nativeEnum(VercelConnectionMethod).array()
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { listProjects as getVercelProjects } from "./vercel-connection-fns";
|
||||
import { TVercelConnection } from "./vercel-connection-types";
|
||||
|
||||
type TGetAppConnectionFunc = (
|
||||
app: AppConnection,
|
||||
connectionId: string,
|
||||
actor: OrgServiceActor
|
||||
) => Promise<TVercelConnection>;
|
||||
|
||||
export const vercelConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||
const listProjects = async (connectionId: string, actor: OrgServiceActor) => {
|
||||
const appConnection = await getAppConnection(AppConnection.Vercel, connectionId, actor);
|
||||
try {
|
||||
const projects = await getVercelProjects(appConnection);
|
||||
return projects;
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to establish connection with Vercel");
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
listProjects
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import z from "zod";
|
||||
|
||||
import { DiscriminativePick } from "@app/lib/types";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import {
|
||||
CreateVercelConnectionSchema,
|
||||
ValidateVercelConnectionCredentialsSchema,
|
||||
VercelConnectionSchema
|
||||
} from "./vercel-connection-schemas";
|
||||
|
||||
export type TVercelConnection = z.infer<typeof VercelConnectionSchema>;
|
||||
|
||||
export type TVercelConnectionInput = z.infer<typeof CreateVercelConnectionSchema> & {
|
||||
app: AppConnection.Vercel;
|
||||
};
|
||||
|
||||
export type TValidateVercelConnectionCredentialsSchema = typeof ValidateVercelConnectionCredentialsSchema;
|
||||
|
||||
export type TVercelConnectionConfig = DiscriminativePick<TVercelConnectionInput, "method" | "app" | "credentials"> & {
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type VercelTeam = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type VercelEnvironment = {
|
||||
id: string;
|
||||
slug: string;
|
||||
type: string;
|
||||
target?: string[];
|
||||
gitBranch?: string;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
};
|
||||
|
||||
export type VercelAppMeta = {
|
||||
githubCommitRef?: string;
|
||||
githubCommitSha?: string;
|
||||
githubCommitMessage?: string;
|
||||
githubCommitAuthorName?: string;
|
||||
};
|
||||
|
||||
export type VercelDeployment = {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
created: number;
|
||||
meta?: VercelAppMeta;
|
||||
target?: "production" | "preview" | "development";
|
||||
};
|
||||
|
||||
export type VercelApp = {
|
||||
name: string;
|
||||
id: string;
|
||||
envs?: VercelEnvironment[];
|
||||
previewBranches?: string[];
|
||||
};
|
||||
|
||||
export type VercelOrgWithApps = VercelTeam & {
|
||||
apps: VercelApp[];
|
||||
};
|
||||
|
||||
export type VercelUserResponse = {
|
||||
user: {
|
||||
id: string;
|
||||
name: string;
|
||||
username: string;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user