General improvements to Vercel Integration

This commit is contained in:
carlosmonastyrski
2025-04-09 11:32:48 -03:00
parent 6dc085b970
commit d01cb282f9
21 changed files with 171 additions and 104 deletions
+5 -1
View File
@@ -1694,6 +1694,9 @@ export const AppConnections = {
sslEnabled: "Whether or not to use SSL when connecting to the database.", sslEnabled: "Whether or not to use SSL when connecting to the database.",
sslRejectUnauthorized: "Whether or not to reject unauthorized SSL certificates.", sslRejectUnauthorized: "Whether or not to reject unauthorized SSL certificates.",
sslCertificate: "The SSL certificate to use for connection." 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.", app: "The ID of the Vercel app to sync secrets to.",
appName: "The name 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.", 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."
} }
} }
}; };
@@ -45,11 +45,11 @@ export const registerVercelConnectionRouter = async (server: FastifyZodProvider)
name: z.string(), name: z.string(),
envs: z envs: z
.object({ .object({
key: z.string(), id: z.string(),
value: z.string(), slug: z.string(),
type: z.string(), type: z.string(),
target: z.array(z.string()).optional(), target: z.array(z.string()).optional(),
gitBranch: z.string().optional(), description: z.string().optional(),
createdAt: z.number().optional(), createdAt: z.number().optional(),
updatedAt: z.number().optional() updatedAt: z.number().optional()
}) })
@@ -136,7 +136,6 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case DatabricksConnectionMethod.ServicePrincipal: case DatabricksConnectionMethod.ServicePrincipal:
return "Service Principal"; return "Service Principal";
case HumanitecConnectionMethod.ApiToken: case HumanitecConnectionMethod.ApiToken:
return "API Token";
case VercelConnectionMethod.ApiToken: case VercelConnectionMethod.ApiToken:
return "API Token"; return "API Token";
case PostgresConnectionMethod.UsernameAndPassword: case PostgresConnectionMethod.UsernameAndPassword:
@@ -133,29 +133,41 @@ async function fetchOrgProjects(orgId: string, apiToken: string): Promise<Vercel
); );
} }
async function fetchProjectEnvironments(projectId: string, apiToken: string): Promise<VercelEnvironment[]> { async function fetchProjectEnvironments(
return fetchAllPages<VercelEnvironment>( projectId: string,
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${projectId}/custom-environments`, teamId: string,
apiToken, apiToken: string
{}, ): Promise<VercelEnvironment[]> {
"environments" 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[]> { async function fetchPreviewBranches(projectId: string, apiToken: string): Promise<string[]> {
const { data } = await request.get<TVercelBranches[]>( try {
`${IntegrationUrls.VERCEL_API_URL}/v1/integrations/git-branches`, const { data } = await request.get<TVercelBranches[]>(
{ `${IntegrationUrls.VERCEL_API_URL}/v1/integrations/git-branches`,
params: { {
projectId params: {
}, projectId
headers: { },
Authorization: `Bearer ${apiToken}`, headers: {
"Accept-Encoding": "application/json" 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 = { type VercelTeam = {
@@ -203,7 +215,7 @@ export const listProjects = async (appConnection: TVercelConnectionInput): Promi
const enhancedProjectsPromises = projects.map(async (project) => { const enhancedProjectsPromises = projects.map(async (project) => {
try { try {
const [environments, previewBranches] = await Promise.all([ const [environments, previewBranches] = await Promise.all([
fetchProjectEnvironments(project.id, apiToken), fetchProjectEnvironments(project.name, org.id, apiToken),
fetchPreviewBranches(project.id, apiToken) fetchPreviewBranches(project.id, apiToken)
]); ]);
@@ -251,9 +263,9 @@ export const getProjectEnvironmentVariables = (project: VercelApp): Record<strin
if (!project.envs) return envVars; if (!project.envs) return envVars;
project.envs.forEach((env) => { project.envs.forEach((env) => {
if (env.value && env.type !== "gitBranch") { if (env.slug && env.type !== "gitBranch") {
const { key, value } = env; const { id, slug } = env;
envVars[key] = value; envVars[id] = slug;
} }
}); });
@@ -28,8 +28,8 @@ export type VercelTeam = {
}; };
export type VercelEnvironment = { export type VercelEnvironment = {
key: string; id: string;
value: string; slug: string;
type: string; type: string;
target?: string[]; target?: string[];
gitBranch?: string; gitBranch?: string;
@@ -1,12 +1,15 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { logger } from "@app/lib/logger";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { VercelEnvironmentType } from "./vercel-sync-enums"; 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 getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => {
const { const {
@@ -22,7 +25,7 @@ const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => {
}; };
const { data } = await request.get<{ envs: VercelApiSecret[] }>( 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, params,
headers: { headers: {
@@ -33,7 +36,12 @@ const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => {
); );
const filteredSecrets = data.envs.filter((secret) => { 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 (secret.target.includes(destinationConfig.env)) {
// If it's preview environment with a branch specified // If it's preview environment with a branch specified
if ( if (
@@ -54,7 +62,7 @@ const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => {
filteredSecrets.map(async (secret) => { filteredSecrets.map(async (secret) => {
if (secret.type === "encrypted") { if (secret.type === "encrypted") {
const { data: decryptedSecret } = await request.get( 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, params,
headers: { headers: {
@@ -82,7 +90,7 @@ const deleteSecret = async (secretSync: TVercelSyncWithCredentials, vercelSecret
try { try {
await request.delete( 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: { headers: {
Authorization: `Bearer ${apiToken}`, Authorization: `Bearer ${apiToken}`,
@@ -108,12 +116,13 @@ const createSecret = async (secretSync: TVercelSyncWithCredentials, secretMap: T
} = secretSync; } = secretSync;
await request.post( 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, key,
value: secretMap[key].value, value: secretMap[key].value,
type: "encrypted", type: "encrypted",
target: [destinationConfig.env], target: isVercelDefaultEnvType(destinationConfig.env) ? [destinationConfig.env] : [],
customEnvironmentIds: !isVercelDefaultEnvType(destinationConfig.env) ? [destinationConfig.env] : [],
...(destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch ...(destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch
? { gitBranch: destinationConfig.branch } ? { gitBranch: destinationConfig.branch }
: {}) : {})
@@ -146,31 +155,37 @@ const updateSecret = async (
} }
} = secretSync; } = secretSync;
// Only update if not sensitive type let target = [...vercelSecret.target];
if (vercelSecret.type !== "sensitive") { if (isVercelDefaultEnvType(destinationConfig.env) && !vercelSecret.target.includes(destinationConfig.env)) {
await request.patch( target = [...target, destinationConfig.env];
`${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 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) { } catch (error) {
throw new SecretSyncError({ throw new SecretSyncError({
error, error,
@@ -15,10 +15,9 @@ import { VercelEnvironmentType } from "./vercel-sync-enums";
const VercelSyncDestinationConfigSchema = z.object({ const VercelSyncDestinationConfigSchema = z.object({
app: z.string().min(1, "App ID is required").describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.app), 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), appName: z.string().min(1, "App Name is required").describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.appName),
env: z env: z.nativeEnum(VercelEnvironmentType).or(z.string()).describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.env),
.enum([VercelEnvironmentType.Development, VercelEnvironmentType.Preview, VercelEnvironmentType.Production]) branch: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.branch),
.describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.env), teamId: z.string().describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.teamId)
branch: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.branch)
}); });
const VercelSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; const VercelSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
@@ -2,6 +2,7 @@ import z from "zod";
import { TVercelConnection } from "@app/services/app-connection/vercel"; import { TVercelConnection } from "@app/services/app-connection/vercel";
import { VercelEnvironmentType } from "./vercel-sync-enums";
import { CreateVercelSyncSchema, VercelSyncListItemSchema, VercelSyncSchema } from "./vercel-sync-schemas"; import { CreateVercelSyncSchema, VercelSyncListItemSchema, VercelSyncSchema } from "./vercel-sync-schemas";
export type TVercelSyncListItem = z.infer<typeof VercelSyncListItemSchema>; export type TVercelSyncListItem = z.infer<typeof VercelSyncListItemSchema>;
@@ -28,9 +29,12 @@ export interface VercelApiSecret {
value: string; value: string;
type: string; type: string;
target: string[]; target: string[];
customEnvironmentIds?: string[];
gitBranch?: string; gitBranch?: string;
createdAt?: number; createdAt?: number;
updatedAt?: number; updatedAt?: number;
configurationId?: string; configurationId?: string;
system?: boolean; system?: boolean;
} }
export type DefaultVercelEnvType = (typeof VercelEnvironmentType)[keyof typeof VercelEnvironmentType];
@@ -1,4 +1,9 @@
--- ---
title: "Create" title: "Create"
openapi: "POST /api/v1/app-connections/vercel" openapi: "POST /api/v1/app-connections/vercel"
--- ---
<Note>
Check out the configuration docs for [Vercel Connections](/integrations/app-connections/vercel) to learn how to obtain
the required credentials.
</Note>
@@ -1,4 +1,9 @@
--- ---
title: "Update" title: "Update"
openapi: "PATCH /api/v1/app-connections/vercel/{connectionId}" openapi: "PATCH /api/v1/app-connections/vercel/{connectionId}"
--- ---
<Note>
Check out the configuration docs for [Vercel Connections](/integrations/app-connections/vercel) to learn how to obtain
the required credentials.
</Note>
@@ -0,0 +1,4 @@
---
title: "Import Secrets"
openapi: "POST /api/v1/secret-syncs/vercel/{syncId}/import-secrets"
---
+5 -1
View File
@@ -3,7 +3,7 @@ title: "Vercel Connection"
description: "Learn how to configure a Vercel Connection for Infisical." 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 ## Setup Vercel Connection in Infisical
@@ -36,18 +36,22 @@ Infisical supports connecting to Vercel using an API Token to securely sync your
<Tabs> <Tabs>
<Tab title="Infisical UI"> <Tab title="Infisical UI">
1. Navigate to App Connections 1. Navigate to App Connections
In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab. In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab.
![App Connections Tab](/images/app-connections/general/add-connection.png) ![App Connections Tab](/images/app-connections/general/add-connection.png)
2. Add Connection 2. Add Connection
Click the **+ Add Connection** button and select the **Vercel Connection** option from the available integrations. 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) ![Select Vercel Connection](/images/app-connections/vercel/vercel-app-connection-option.png)
3. Fill the Vercel Connection Modal 3. Fill the Vercel Connection Modal
Complete the Vercel Connection form by entering: Complete the Vercel Connection form by entering:
- A descriptive name for the connection - A descriptive name for the connection
- The API Token you generated in steps 3-4 - The API Token you generated in steps 3-4
- An optional description for future reference - An optional description for future reference
![Vercel Connection Modal](/images/app-connections/vercel/vercel-app-connection-modal.png) ![Vercel Connection Modal](/images/app-connections/vercel/vercel-app-connection-modal.png)
4. Connection Created 4. Connection Created
After clicking Create, your **Vercel Connection** is established and ready to use with your Infisical projects. 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) ![Vercel Connection Created](/images/app-connections/vercel/vercel-app-connection-created.png)
</Tab> </Tab>
+2 -3
View File
@@ -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. - **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. - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
<Note> - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Vercel when keys conflict.
Vercel does not support importing secrets. - **Import Secrets (Prioritize Vercel)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Vercel over Infisical when keys conflict.
</Note>
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. - **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. - **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.
+2 -1
View File
@@ -1123,7 +1123,8 @@
"api-reference/endpoints/secret-syncs/vercel/update", "api-reference/endpoints/secret-syncs/vercel/update",
"api-reference/endpoints/secret-syncs/vercel/delete", "api-reference/endpoints/secret-syncs/vercel/delete",
"api-reference/endpoints/secret-syncs/vercel/sync-secrets", "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"
] ]
} }
] ]
@@ -1,3 +1,4 @@
import { useMemo } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form"; import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select"; import { SingleValue } from "react-select";
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"; import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
@@ -39,13 +40,26 @@ export const VercelSyncFields = () => {
?.find((project) => project.apps.some((app) => app.id === currentApp)) ?.find((project) => project.apps.some((app) => app.id === currentApp))
?.apps.find((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) => ({ const environmentOptions = useMemo(() => {
key: env.slug, return vercelEnvironments
type: env.slug, .map((env) => ({
name: env.name 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 = const previewBranchOptions =
selectedProject?.previewBranches?.map((branch) => ({ selectedProject?.previewBranches?.map((branch) => ({
@@ -77,7 +91,7 @@ export const VercelSyncFields = () => {
helperText={ helperText={
<Tooltip <Tooltip
className="max-w-md" className="max-w-md"
content="Ensure that the project exists and the service account used on this connection has write permissions for the specified project." content="Ensure the project exists and the API token scope for this connection includes the desired project."
> >
<div> <div>
<span>Don&#39;t see the project you&#39;re looking for?</span>{" "} <span>Don&#39;t see the project you&#39;re looking for?</span>{" "}
@@ -95,6 +109,10 @@ export const VercelSyncFields = () => {
const appId = (option as SingleValue<TVercelConnectionApp>)?.id ?? null; const appId = (option as SingleValue<TVercelConnectionApp>)?.id ?? null;
onChange(appId); onChange(appId);
setValue("destinationConfig.branch", ""); setValue("destinationConfig.branch", "");
setValue(
"destinationConfig.teamId",
(option as SingleValue<TVercelConnectionApp>)?.projectId || ""
);
setValue( setValue(
"destinationConfig.appName", "destinationConfig.appName",
(option as SingleValue<TVercelConnectionApp>)?.name || "" (option as SingleValue<TVercelConnectionApp>)?.name || ""
@@ -104,6 +122,7 @@ export const VercelSyncFields = () => {
placeholder="Select a project..." placeholder="Select a project..."
getOptionLabel={(option) => option.name} getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id.toString()} getOptionValue={(option) => option.id.toString()}
groupBy="project"
/> />
</FormControl> </FormControl>
)} )}
@@ -124,9 +143,9 @@ export const VercelSyncFields = () => {
value={ value={
value value
? { ? {
key: value, key: environmentOptions.find((env) => env.key === value)?.key,
type: value, type: environmentOptions.find((env) => env.key === value)?.type,
name: vercelEnvironments.find((env) => env.slug === value)?.name || value name: environmentOptions.find((env) => env.key === value)?.name
} }
: null : null
} }
@@ -138,8 +157,8 @@ export const VercelSyncFields = () => {
}} }}
options={environmentOptions} options={environmentOptions}
placeholder="Select an environment..." placeholder="Select an environment..."
getOptionLabel={(option) => option.name || option.key} getOptionLabel={(option) => option.name || option.key || ""}
getOptionValue={(option) => option.key} getOptionValue={(option) => option.key || ""}
/> />
</FormControl> </FormControl>
)} )}
@@ -10,17 +10,9 @@ export const VercelSyncDestinationSchema = BaseSecretSyncSchema().merge(
destinationConfig: z.object({ destinationConfig: z.object({
app: z.string().trim().min(1, "Project required"), app: z.string().trim().min(1, "Project required"),
appName: z.string().trim().min(1, "Project required"), appName: z.string().trim().min(1, "Project required"),
env: z.enum( env: z.nativeEnum(VercelEnvironmentType).or(z.string()),
[ branch: z.string().trim().optional(),
VercelEnvironmentType.Development, teamId: z.string().trim()
VercelEnvironmentType.Preview,
VercelEnvironmentType.Production
],
{
required_error: "Environment is required"
}
),
branch: z.string().trim().optional()
}) })
}) })
); );
+1 -2
View File
@@ -52,9 +52,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
case DatabricksConnectionMethod.ServicePrincipal: case DatabricksConnectionMethod.ServicePrincipal:
return { name: "Service Principal", icon: faUser }; return { name: "Service Principal", icon: faUser };
case HumanitecConnectionMethod.ApiToken: case HumanitecConnectionMethod.ApiToken:
return { name: "API Token", icon: faKey };
case VercelConnectionMethod.ApiToken: case VercelConnectionMethod.ApiToken:
return { name: "Service API Token", icon: faKey }; return { name: "API Token", icon: faKey };
case PostgresConnectionMethod.UsernameAndPassword: case PostgresConnectionMethod.UsernameAndPassword:
case MsSqlConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword:
return { name: "Username & Password", icon: faLock }; return { name: "Username & Password", icon: faLock };
@@ -5,8 +5,8 @@ export type TVercelApp = {
}; };
export type TVercelConnectionEnvironment = { export type TVercelConnectionEnvironment = {
key: string; id: string;
value: string; slug: string;
type: string; type: string;
target?: string[]; target?: string[];
gitBranch?: string; gitBranch?: string;
@@ -19,6 +19,7 @@ export type TVercelConnectionApp = {
name: string; name: string;
envs?: TVercelConnectionEnvironment[]; envs?: TVercelConnectionEnvironment[];
previewBranches?: string[]; previewBranches?: string[];
projectId: string;
}; };
export type TVercelConnectionOrganization = { export type TVercelConnectionOrganization = {
@@ -14,9 +14,10 @@ export type TVercelSync = TRootSecretSync & {
destination: SecretSync.Vercel; destination: SecretSync.Vercel;
destinationConfig: { destinationConfig: {
app: string; app: string;
env: VercelEnvironment; env: VercelEnvironment | string;
branch?: string; branch?: string;
appName?: string; appName?: string;
teamId: string;
}; };
connection: { connection: {
app: AppConnection.Vercel; app: AppConnection.Vercel;
@@ -102,7 +102,7 @@ export const VercelConnectionForm = ({ appConnection, onSubmit }: Props) => {
<FormControl <FormControl
errorText={error?.message} errorText={error?.message}
isError={Boolean(error?.message)} isError={Boolean(error?.message)}
label="Service API Token" label="API Token"
> >
<SecretInput <SecretInput
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5" containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
@@ -14,7 +14,9 @@ export const VercelSyncDestinationSection = ({ secretSync }: Props) => {
if (destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch) { if (destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch) {
Components = ( Components = (
<> <>
<GenericFieldLabel label="Vercel App">{destinationConfig.app}</GenericFieldLabel> <GenericFieldLabel label="Vercel App">
{destinationConfig.appName || destinationConfig.app}
</GenericFieldLabel>
<GenericFieldLabel label="Environment">{destinationConfig.env}</GenericFieldLabel> <GenericFieldLabel label="Environment">{destinationConfig.env}</GenericFieldLabel>
<GenericFieldLabel label="Preview Branch">{destinationConfig.branch}</GenericFieldLabel> <GenericFieldLabel label="Preview Branch">{destinationConfig.branch}</GenericFieldLabel>
</> </>
@@ -22,7 +24,9 @@ export const VercelSyncDestinationSection = ({ secretSync }: Props) => {
} else { } else {
Components = ( Components = (
<> <>
<GenericFieldLabel label="Vercel App">{destinationConfig.app}</GenericFieldLabel> <GenericFieldLabel label="Vercel App">
{destinationConfig.appName || destinationConfig.app}
</GenericFieldLabel>
<GenericFieldLabel label="Environment">{destinationConfig.env}</GenericFieldLabel> <GenericFieldLabel label="Environment">{destinationConfig.env}</GenericFieldLabel>
</> </>
); );