From d01cb282f9cb3852eb55185873fcad28e4a9c7e7 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Wed, 9 Apr 2025 11:32:48 -0300 Subject: [PATCH] General improvements to Vercel Integration --- backend/src/lib/api-docs/constants.ts | 6 +- .../vercel-connection-router.ts | 6 +- .../app-connection/app-connection-fns.ts | 1 - .../vercel/vercel-connection-fns.ts | 58 ++++++++------ .../vercel/vercel-connection-types.ts | 4 +- .../secret-sync/vercel/vercel-sync-fns.ts | 79 +++++++++++-------- .../secret-sync/vercel/vercel-sync-schemas.ts | 7 +- .../secret-sync/vercel/vercel-sync-types.ts | 4 + .../app-connections/vercel/create.mdx | 7 +- .../app-connections/vercel/update.mdx | 7 +- .../secret-syncs/vercel/import-secrets.mdx | 4 + docs/integrations/app-connections/vercel.mdx | 6 +- docs/integrations/secret-syncs/vercel.mdx | 5 +- docs/mint.json | 3 +- .../VercelSyncFields.tsx | 43 +++++++--- .../schemas/vercel-sync-destination-schema.ts | 14 +--- frontend/src/helpers/appConnections.ts | 3 +- .../hooks/api/appConnections/vercel/types.ts | 5 +- .../api/secretSyncs/types/vercel-sync.ts | 3 +- .../VercelConnectionForm.tsx | 2 +- .../VercelSyncDestinationSection.tsx | 8 +- 21 files changed, 171 insertions(+), 104 deletions(-) create mode 100644 docs/api-reference/endpoints/secret-syncs/vercel/import-secrets.mdx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 813373e4b..4220842c6 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1694,6 +1694,9 @@ export const AppConnections = { sslEnabled: "Whether or not to use SSL when connecting to the database.", sslRejectUnauthorized: "Whether or not to reject unauthorized SSL certificates.", sslCertificate: "The SSL certificate to use for connection." + }, + VERCEL: { + apiToken: "The API token used to authenticate with Vercel." } } }; @@ -1814,7 +1817,8 @@ export const SecretSyncs = { app: "The ID of the Vercel app to sync secrets to.", appName: "The name of the Vercel app to sync secrets to.", env: "The ID of the Vercel environment to sync secrets to.", - branch: "The branch to sync preview secrets to." + branch: "The branch to sync preview secrets to.", + teamId: "The ID of the Vercel team to sync secrets to." } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/vercel-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/vercel-connection-router.ts index c4ee47607..079870305 100644 --- a/backend/src/server/routes/v1/app-connection-routers/vercel-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/vercel-connection-router.ts @@ -45,11 +45,11 @@ export const registerVercelConnectionRouter = async (server: FastifyZodProvider) name: z.string(), envs: z .object({ - key: z.string(), - value: z.string(), + id: z.string(), + slug: z.string(), type: z.string(), target: z.array(z.string()).optional(), - gitBranch: z.string().optional(), + description: z.string().optional(), createdAt: z.number().optional(), updatedAt: z.number().optional() }) diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index ddba0662d..cc7146151 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -136,7 +136,6 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) => case DatabricksConnectionMethod.ServicePrincipal: return "Service Principal"; case HumanitecConnectionMethod.ApiToken: - return "API Token"; case VercelConnectionMethod.ApiToken: return "API Token"; case PostgresConnectionMethod.UsernameAndPassword: diff --git a/backend/src/services/app-connection/vercel/vercel-connection-fns.ts b/backend/src/services/app-connection/vercel/vercel-connection-fns.ts index 4fd5e9ff4..daf7a713f 100644 --- a/backend/src/services/app-connection/vercel/vercel-connection-fns.ts +++ b/backend/src/services/app-connection/vercel/vercel-connection-fns.ts @@ -133,29 +133,41 @@ async function fetchOrgProjects(orgId: string, apiToken: string): Promise { - return fetchAllPages( - `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${projectId}/custom-environments`, - apiToken, - {}, - "environments" - ); +async function fetchProjectEnvironments( + projectId: string, + teamId: string, + apiToken: string +): Promise { + try { + return await fetchAllPages( + `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${projectId}/custom-environments?teamId=${teamId}`, + apiToken, + {}, + "environments" + ); + } catch (error) { + return []; + } } async function fetchPreviewBranches(projectId: string, apiToken: string): Promise { - const { data } = await request.get( - `${IntegrationUrls.VERCEL_API_URL}/v1/integrations/git-branches`, - { - params: { - projectId - }, - headers: { - Authorization: `Bearer ${apiToken}`, - "Accept-Encoding": "application/json" + try { + const { data } = await request.get( + `${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); + ); + return data.filter((b) => b.ref !== "main").map((b) => b.ref); + } catch (error) { + return []; + } } type VercelTeam = { @@ -203,7 +215,7 @@ export const listProjects = async (appConnection: TVercelConnectionInput): Promi const enhancedProjectsPromises = projects.map(async (project) => { try { const [environments, previewBranches] = await Promise.all([ - fetchProjectEnvironments(project.id, apiToken), + fetchProjectEnvironments(project.name, org.id, apiToken), fetchPreviewBranches(project.id, apiToken) ]); @@ -251,9 +263,9 @@ export const getProjectEnvironmentVariables = (project: VercelApp): Record { - if (env.value && env.type !== "gitBranch") { - const { key, value } = env; - envVars[key] = value; + if (env.slug && env.type !== "gitBranch") { + const { id, slug } = env; + envVars[id] = slug; } }); diff --git a/backend/src/services/app-connection/vercel/vercel-connection-types.ts b/backend/src/services/app-connection/vercel/vercel-connection-types.ts index 528917b3a..4ab69d1df 100644 --- a/backend/src/services/app-connection/vercel/vercel-connection-types.ts +++ b/backend/src/services/app-connection/vercel/vercel-connection-types.ts @@ -28,8 +28,8 @@ export type VercelTeam = { }; export type VercelEnvironment = { - key: string; - value: string; + id: string; + slug: string; type: string; target?: string[]; gitBranch?: string; diff --git a/backend/src/services/secret-sync/vercel/vercel-sync-fns.ts b/backend/src/services/secret-sync/vercel/vercel-sync-fns.ts index 7cca78f4e..21963737f 100644 --- a/backend/src/services/secret-sync/vercel/vercel-sync-fns.ts +++ b/backend/src/services/secret-sync/vercel/vercel-sync-fns.ts @@ -1,12 +1,15 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { request } from "@app/lib/config/request"; -import { logger } from "@app/lib/logger"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { VercelEnvironmentType } from "./vercel-sync-enums"; -import { TVercelSyncWithCredentials, VercelApiSecret } from "./vercel-sync-types"; +import { DefaultVercelEnvType, TVercelSyncWithCredentials, VercelApiSecret } from "./vercel-sync-types"; + +function isVercelDefaultEnvType(value: string): value is DefaultVercelEnvType { + return Object.values(VercelEnvironmentType).map(String).includes(value); +} const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => { const { @@ -22,7 +25,7 @@ const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => { }; const { data } = await request.get<{ envs: VercelApiSecret[] }>( - `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env`, + `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env?teamId=${destinationConfig.teamId}`, { params, headers: { @@ -33,7 +36,12 @@ const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => { ); const filteredSecrets = data.envs.filter((secret) => { - // For environment-specific filtering + if (!isVercelDefaultEnvType(destinationConfig.env)) { + if (secret.customEnvironmentIds?.includes(destinationConfig.env)) { + return true; + } + return false; + } if (secret.target.includes(destinationConfig.env)) { // If it's preview environment with a branch specified if ( @@ -54,7 +62,7 @@ const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => { filteredSecrets.map(async (secret) => { if (secret.type === "encrypted") { const { data: decryptedSecret } = await request.get( - `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${secret.id}`, + `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${secret.id}?teamId=${destinationConfig.teamId}`, { params, headers: { @@ -82,7 +90,7 @@ const deleteSecret = async (secretSync: TVercelSyncWithCredentials, vercelSecret try { await request.delete( - `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${vercelSecret.id}`, + `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${vercelSecret.id}?teamId=${destinationConfig.teamId}`, { headers: { Authorization: `Bearer ${apiToken}`, @@ -108,12 +116,13 @@ const createSecret = async (secretSync: TVercelSyncWithCredentials, secretMap: T } = secretSync; await request.post( - `${IntegrationUrls.VERCEL_API_URL}/v10/projects/${destinationConfig.app}/env`, + `${IntegrationUrls.VERCEL_API_URL}/v10/projects/${destinationConfig.app}/env?teamId=${destinationConfig.teamId}`, { key, value: secretMap[key].value, type: "encrypted", - target: [destinationConfig.env], + target: isVercelDefaultEnvType(destinationConfig.env) ? [destinationConfig.env] : [], + customEnvironmentIds: !isVercelDefaultEnvType(destinationConfig.env) ? [destinationConfig.env] : [], ...(destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch ? { gitBranch: destinationConfig.branch } : {}) @@ -146,31 +155,37 @@ const updateSecret = async ( } } = secretSync; - // Only update if not sensitive type - if (vercelSecret.type !== "sensitive") { - await request.patch( - `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${vercelSecret.id}`, - { - key: vercelSecret.key, - value: secretMap[vercelSecret.key].value, - type: vercelSecret.type, - target: vercelSecret.target.includes(destinationConfig.env) - ? [...vercelSecret.target] - : [...vercelSecret.target, destinationConfig.env], - ...(destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch - ? { gitBranch: destinationConfig.branch } - : {}) - }, - { - headers: { - Authorization: `Bearer ${apiToken}`, - "Accept-Encoding": "application/json" - } - } - ); - } else { - logger.info(`Vercel secret ${vercelSecret.key} is of type 'sensitive' and cannot be updated through the API`); + let target = [...vercelSecret.target]; + if (isVercelDefaultEnvType(destinationConfig.env) && !vercelSecret.target.includes(destinationConfig.env)) { + target = [...target, destinationConfig.env]; } + let customEnvironmentIds = [...(vercelSecret.customEnvironmentIds || [])]; + if ( + !isVercelDefaultEnvType(destinationConfig.env) && + !vercelSecret.customEnvironmentIds?.includes(destinationConfig.env) + ) { + customEnvironmentIds = [...customEnvironmentIds, destinationConfig.env]; + } + + await request.patch( + `${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${vercelSecret.id}?teamId=${destinationConfig.teamId}`, + { + key: vercelSecret.key, + value: secretMap[vercelSecret.key].value, + type: vercelSecret.type, + target, + customEnvironmentIds, + ...(destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch + ? { gitBranch: destinationConfig.branch } + : {}) + }, + { + headers: { + Authorization: `Bearer ${apiToken}`, + "Accept-Encoding": "application/json" + } + } + ); } catch (error) { throw new SecretSyncError({ error, diff --git a/backend/src/services/secret-sync/vercel/vercel-sync-schemas.ts b/backend/src/services/secret-sync/vercel/vercel-sync-schemas.ts index fdab43391..84d7a6da4 100644 --- a/backend/src/services/secret-sync/vercel/vercel-sync-schemas.ts +++ b/backend/src/services/secret-sync/vercel/vercel-sync-schemas.ts @@ -15,10 +15,9 @@ import { VercelEnvironmentType } from "./vercel-sync-enums"; const VercelSyncDestinationConfigSchema = z.object({ app: z.string().min(1, "App ID is required").describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.app), appName: z.string().min(1, "App Name is required").describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.appName), - env: z - .enum([VercelEnvironmentType.Development, VercelEnvironmentType.Preview, VercelEnvironmentType.Production]) - .describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.env), - branch: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.branch) + env: z.nativeEnum(VercelEnvironmentType).or(z.string()).describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.env), + branch: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.branch), + teamId: z.string().describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.teamId) }); const VercelSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; diff --git a/backend/src/services/secret-sync/vercel/vercel-sync-types.ts b/backend/src/services/secret-sync/vercel/vercel-sync-types.ts index 50abf4a82..d6d2b6433 100644 --- a/backend/src/services/secret-sync/vercel/vercel-sync-types.ts +++ b/backend/src/services/secret-sync/vercel/vercel-sync-types.ts @@ -2,6 +2,7 @@ import z from "zod"; import { TVercelConnection } from "@app/services/app-connection/vercel"; +import { VercelEnvironmentType } from "./vercel-sync-enums"; import { CreateVercelSyncSchema, VercelSyncListItemSchema, VercelSyncSchema } from "./vercel-sync-schemas"; export type TVercelSyncListItem = z.infer; @@ -28,9 +29,12 @@ export interface VercelApiSecret { value: string; type: string; target: string[]; + customEnvironmentIds?: string[]; gitBranch?: string; createdAt?: number; updatedAt?: number; configurationId?: string; system?: boolean; } + +export type DefaultVercelEnvType = (typeof VercelEnvironmentType)[keyof typeof VercelEnvironmentType]; diff --git a/docs/api-reference/endpoints/app-connections/vercel/create.mdx b/docs/api-reference/endpoints/app-connections/vercel/create.mdx index cb2f22f7b..63998ac32 100644 --- a/docs/api-reference/endpoints/app-connections/vercel/create.mdx +++ b/docs/api-reference/endpoints/app-connections/vercel/create.mdx @@ -1,4 +1,9 @@ --- title: "Create" openapi: "POST /api/v1/app-connections/vercel" ---- \ No newline at end of file +--- + + + Check out the configuration docs for [Vercel Connections](/integrations/app-connections/vercel) to learn how to obtain + the required credentials. + \ No newline at end of file diff --git a/docs/api-reference/endpoints/app-connections/vercel/update.mdx b/docs/api-reference/endpoints/app-connections/vercel/update.mdx index b9b46c164..d0e2f4ae2 100644 --- a/docs/api-reference/endpoints/app-connections/vercel/update.mdx +++ b/docs/api-reference/endpoints/app-connections/vercel/update.mdx @@ -1,4 +1,9 @@ --- title: "Update" openapi: "PATCH /api/v1/app-connections/vercel/{connectionId}" ---- \ No newline at end of file +--- + + + Check out the configuration docs for [Vercel Connections](/integrations/app-connections/vercel) to learn how to obtain + the required credentials. + \ No newline at end of file diff --git a/docs/api-reference/endpoints/secret-syncs/vercel/import-secrets.mdx b/docs/api-reference/endpoints/secret-syncs/vercel/import-secrets.mdx new file mode 100644 index 000000000..807eb2850 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/vercel/import-secrets.mdx @@ -0,0 +1,4 @@ +--- +title: "Import Secrets" +openapi: "POST /api/v1/secret-syncs/vercel/{syncId}/import-secrets" +--- diff --git a/docs/integrations/app-connections/vercel.mdx b/docs/integrations/app-connections/vercel.mdx index 7c0a30e87..8ef4a5647 100644 --- a/docs/integrations/app-connections/vercel.mdx +++ b/docs/integrations/app-connections/vercel.mdx @@ -3,7 +3,7 @@ title: "Vercel Connection" description: "Learn how to configure a Vercel Connection for Infisical." --- -Infisical supports connecting to Vercel using an API Token to securely sync your secrets between both platforms. +Infisical supports connecting to Vercel using an API Token to securely sync your secrets to Vercel. ## Setup Vercel Connection in Infisical @@ -36,18 +36,22 @@ Infisical supports connecting to Vercel using an API Token to securely sync your 1. Navigate to App Connections + In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Add Connection + Click the **+ Add Connection** button and select the **Vercel Connection** option from the available integrations. ![Select Vercel Connection](/images/app-connections/vercel/vercel-app-connection-option.png) 3. Fill the Vercel Connection Modal + Complete the Vercel Connection form by entering: - A descriptive name for the connection - The API Token you generated in steps 3-4 - An optional description for future reference ![Vercel Connection Modal](/images/app-connections/vercel/vercel-app-connection-modal.png) 4. Connection Created + After clicking Create, your **Vercel Connection** is established and ready to use with your Infisical projects. ![Vercel Connection Created](/images/app-connections/vercel/vercel-app-connection-created.png) diff --git a/docs/integrations/secret-syncs/vercel.mdx b/docs/integrations/secret-syncs/vercel.mdx index 7384121c6..735877f8e 100644 --- a/docs/integrations/secret-syncs/vercel.mdx +++ b/docs/integrations/secret-syncs/vercel.mdx @@ -40,9 +40,8 @@ description: "Learn how to configure a Vercel Sync for Infisical." - **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - - Vercel does not support importing secrets. - + - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Vercel when keys conflict. + - **Import Secrets (Prioritize Vercel)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Vercel over Infisical when keys conflict. - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. diff --git a/docs/mint.json b/docs/mint.json index da2c1997e..bb6418a11 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -1123,7 +1123,8 @@ "api-reference/endpoints/secret-syncs/vercel/update", "api-reference/endpoints/secret-syncs/vercel/delete", "api-reference/endpoints/secret-syncs/vercel/sync-secrets", - "api-reference/endpoints/secret-syncs/vercel/remove-secrets" + "api-reference/endpoints/secret-syncs/vercel/remove-secrets", + "api-reference/endpoints/secret-syncs/vercel/import-secrets" ] } ] diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/VercelSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/VercelSyncFields.tsx index 1fa36e0d0..708284279 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/VercelSyncFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/VercelSyncFields.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Controller, useFormContext, useWatch } from "react-hook-form"; import { SingleValue } from "react-select"; import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"; @@ -39,13 +40,26 @@ export const VercelSyncFields = () => { ?.find((project) => project.apps.some((app) => app.id === currentApp)) ?.apps.find((app) => app.id === currentApp); - const allApps = projects?.flatMap((project) => project.apps) || []; + const allApps = + projects?.flatMap((project) => + project.apps.map((app) => ({ ...app, project: project.name, projectId: project.id })) + ) || []; - const environmentOptions = vercelEnvironments.map((env) => ({ - key: env.slug, - type: env.slug, - name: env.name - })); + const environmentOptions = useMemo(() => { + return vercelEnvironments + .map((env) => ({ + key: env.slug, + type: env.slug, + name: env.name + })) + .concat( + selectedProject?.envs?.map((env) => ({ + key: env.id, + type: env.type, + name: env.slug + })) || [] + ); + }, [currentApp]); const previewBranchOptions = selectedProject?.previewBranches?.map((branch) => ({ @@ -77,7 +91,7 @@ export const VercelSyncFields = () => { helperText={
Don't see the project you're looking for?{" "} @@ -95,6 +109,10 @@ export const VercelSyncFields = () => { const appId = (option as SingleValue)?.id ?? null; onChange(appId); setValue("destinationConfig.branch", ""); + setValue( + "destinationConfig.teamId", + (option as SingleValue)?.projectId || "" + ); setValue( "destinationConfig.appName", (option as SingleValue)?.name || "" @@ -104,6 +122,7 @@ export const VercelSyncFields = () => { placeholder="Select a project..." getOptionLabel={(option) => option.name} getOptionValue={(option) => option.id.toString()} + groupBy="project" /> )} @@ -124,9 +143,9 @@ export const VercelSyncFields = () => { value={ value ? { - key: value, - type: value, - name: vercelEnvironments.find((env) => env.slug === value)?.name || value + key: environmentOptions.find((env) => env.key === value)?.key, + type: environmentOptions.find((env) => env.key === value)?.type, + name: environmentOptions.find((env) => env.key === value)?.name } : null } @@ -138,8 +157,8 @@ export const VercelSyncFields = () => { }} options={environmentOptions} placeholder="Select an environment..." - getOptionLabel={(option) => option.name || option.key} - getOptionValue={(option) => option.key} + getOptionLabel={(option) => option.name || option.key || ""} + getOptionValue={(option) => option.key || ""} /> )} diff --git a/frontend/src/components/secret-syncs/forms/schemas/vercel-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/vercel-sync-destination-schema.ts index 234e7fa66..9d3678803 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/vercel-sync-destination-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/vercel-sync-destination-schema.ts @@ -10,17 +10,9 @@ export const VercelSyncDestinationSchema = BaseSecretSyncSchema().merge( destinationConfig: z.object({ app: z.string().trim().min(1, "Project required"), appName: z.string().trim().min(1, "Project required"), - env: z.enum( - [ - VercelEnvironmentType.Development, - VercelEnvironmentType.Preview, - VercelEnvironmentType.Production - ], - { - required_error: "Environment is required" - } - ), - branch: z.string().trim().optional() + env: z.nativeEnum(VercelEnvironmentType).or(z.string()), + branch: z.string().trim().optional(), + teamId: z.string().trim() }) }) ); diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index 4dadf2b7d..286f2cb3f 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -52,9 +52,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) case DatabricksConnectionMethod.ServicePrincipal: return { name: "Service Principal", icon: faUser }; case HumanitecConnectionMethod.ApiToken: - return { name: "API Token", icon: faKey }; case VercelConnectionMethod.ApiToken: - return { name: "Service API Token", icon: faKey }; + return { name: "API Token", icon: faKey }; case PostgresConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword: return { name: "Username & Password", icon: faLock }; diff --git a/frontend/src/hooks/api/appConnections/vercel/types.ts b/frontend/src/hooks/api/appConnections/vercel/types.ts index 82b48dc7f..1e25ce9ee 100644 --- a/frontend/src/hooks/api/appConnections/vercel/types.ts +++ b/frontend/src/hooks/api/appConnections/vercel/types.ts @@ -5,8 +5,8 @@ export type TVercelApp = { }; export type TVercelConnectionEnvironment = { - key: string; - value: string; + id: string; + slug: string; type: string; target?: string[]; gitBranch?: string; @@ -19,6 +19,7 @@ export type TVercelConnectionApp = { name: string; envs?: TVercelConnectionEnvironment[]; previewBranches?: string[]; + projectId: string; }; export type TVercelConnectionOrganization = { diff --git a/frontend/src/hooks/api/secretSyncs/types/vercel-sync.ts b/frontend/src/hooks/api/secretSyncs/types/vercel-sync.ts index 5a1869a3d..ffae61e23 100644 --- a/frontend/src/hooks/api/secretSyncs/types/vercel-sync.ts +++ b/frontend/src/hooks/api/secretSyncs/types/vercel-sync.ts @@ -14,9 +14,10 @@ export type TVercelSync = TRootSecretSync & { destination: SecretSync.Vercel; destinationConfig: { app: string; - env: VercelEnvironment; + env: VercelEnvironment | string; branch?: string; appName?: string; + teamId: string; }; connection: { app: AppConnection.Vercel; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/VercelConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/VercelConnectionForm.tsx index 88c3c2d8f..d655a5c6a 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/VercelConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/VercelConnectionForm.tsx @@ -102,7 +102,7 @@ export const VercelConnectionForm = ({ appConnection, onSubmit }: Props) => { { if (destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch) { Components = ( <> - {destinationConfig.app} + + {destinationConfig.appName || destinationConfig.app} + {destinationConfig.env} {destinationConfig.branch} @@ -22,7 +24,9 @@ export const VercelSyncDestinationSection = ({ secretSync }: Props) => { } else { Components = ( <> - {destinationConfig.app} + + {destinationConfig.appName || destinationConfig.app} + {destinationConfig.env} );