-
Notifications
You must be signed in to change notification settings - Fork 131
feat: add Webflow plugin #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export type WebflowCredentials = { | ||
| WEBFLOW_API_KEY?: string; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| export function WebflowIcon({ className }: { className?: string }) { | ||
| return ( | ||
| <svg | ||
| aria-label="Webflow logo" | ||
| className={className} | ||
| fill="currentColor" | ||
| viewBox="0 0 24 24" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>Webflow</title> | ||
| <path d="m24 4.515-7.658 14.97H9.149l3.205-6.204h-.144C9.566 16.713 5.621 18.973 0 19.485v-6.118s3.596-.213 5.71-2.435H0V4.515h6.417v5.278l.144-.001 2.622-5.277h4.854v5.244h.144l2.72-5.244H24Z"/> | ||
| </svg> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import type { IntegrationPlugin } from "../registry"; | ||
| import { registerIntegration } from "../registry"; | ||
| import { WebflowIcon } from "./icon"; | ||
|
|
||
| const webflowPlugin: IntegrationPlugin = { | ||
| type: "webflow", | ||
| label: "Webflow", | ||
| description: "Publish and manage Webflow sites", | ||
|
|
||
| icon: WebflowIcon, | ||
|
|
||
| formFields: [ | ||
| { | ||
| id: "apiKey", | ||
| label: "API Token", | ||
| type: "password", | ||
| placeholder: "your-api-token", | ||
| configKey: "apiKey", | ||
| envVar: "WEBFLOW_API_KEY", | ||
| helpText: "Generate an API token from ", | ||
| helpLink: { | ||
| text: "Webflow Dashboard", | ||
| url: "https://webflow.com/dashboard", | ||
| }, | ||
| }, | ||
| ], | ||
|
|
||
| testConfig: { | ||
| getTestFunction: async () => { | ||
| const { testWebflow } = await import("./test"); | ||
| return testWebflow; | ||
| }, | ||
| }, | ||
|
|
||
| actions: [ | ||
| { | ||
| slug: "list-sites", | ||
| label: "List Sites", | ||
| description: "Get all sites accessible with the API token", | ||
| category: "Webflow", | ||
| stepFunction: "listSitesStep", | ||
| stepImportPath: "list-sites", | ||
| outputFields: [ | ||
| { field: "sites", description: "Array of site objects" }, | ||
| { field: "count", description: "Number of sites returned" }, | ||
| ], | ||
| configFields: [], | ||
| }, | ||
| { | ||
| slug: "get-site", | ||
| label: "Get Site", | ||
| description: "Get details of a specific Webflow site", | ||
| category: "Webflow", | ||
| stepFunction: "getSiteStep", | ||
| stepImportPath: "get-site", | ||
| outputFields: [ | ||
| { field: "id", description: "Site ID" }, | ||
| { field: "displayName", description: "Display name of the site" }, | ||
| { field: "shortName", description: "Short name (subdomain)" }, | ||
| { field: "previewUrl", description: "Preview URL" }, | ||
| { field: "lastPublished", description: "Last published timestamp" }, | ||
| { field: "customDomains", description: "Array of custom domains" }, | ||
| ], | ||
| configFields: [ | ||
| { | ||
| key: "siteId", | ||
| label: "Site ID", | ||
| type: "template-input", | ||
| placeholder: "site-id or {{NodeName.id}}", | ||
| example: "580e63e98c9a982ac9b8b741", | ||
| required: true, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| slug: "publish-site", | ||
| label: "Publish Site", | ||
| description: "Publish a site to one or more domains", | ||
| category: "Webflow", | ||
| stepFunction: "publishSiteStep", | ||
| stepImportPath: "publish-site", | ||
| outputFields: [ | ||
| { field: "publishedDomains", description: "Array of published domain URLs" }, | ||
| { field: "publishedToSubdomain", description: "Whether published to Webflow subdomain" }, | ||
| ], | ||
| configFields: [ | ||
| { | ||
| key: "siteId", | ||
| label: "Site ID", | ||
| type: "template-input", | ||
| placeholder: "site-id or {{NodeName.id}}", | ||
| example: "580e63e98c9a982ac9b8b741", | ||
| required: true, | ||
| }, | ||
| { | ||
| key: "publishToWebflowSubdomain", | ||
| label: "Publish to Webflow Subdomain", | ||
| type: "select", | ||
| options: [ | ||
| { value: "true", label: "Yes" }, | ||
| { value: "false", label: "No" }, | ||
| ], | ||
| defaultValue: "true", | ||
| }, | ||
| { | ||
| key: "customDomainIds", | ||
| label: "Custom Domain IDs (comma-separated)", | ||
| type: "template-input", | ||
| placeholder: "domain-id-1, domain-id-2", | ||
| example: "589a331aa51e760df7ccb89d", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| registerIntegration(webflowPlugin); | ||
|
|
||
| export default webflowPlugin; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| import "server-only"; | ||
|
|
||
| import { fetchCredentials } from "@/lib/credential-fetcher"; | ||
| import { type StepInput, withStepLogging } from "@/lib/steps/step-handler"; | ||
| import { getErrorMessage } from "@/lib/utils"; | ||
| import type { WebflowCredentials } from "../credentials"; | ||
|
|
||
| const WEBFLOW_API_URL = "https://api.webflow.com/v2"; | ||
|
|
||
| type WebflowSiteResponse = { | ||
| id: string; | ||
| workspaceId: string; | ||
| createdOn: string; | ||
| displayName: string; | ||
| shortName: string; | ||
| lastPublished?: string; | ||
| lastUpdated: string; | ||
| previewUrl: string; | ||
| timeZone: string; | ||
| customDomains?: Array<{ | ||
| id: string; | ||
| url: string; | ||
| lastPublished?: string; | ||
| }>; | ||
| }; | ||
|
|
||
| type GetSiteResult = | ||
| | { | ||
| success: true; | ||
| id: string; | ||
| displayName: string; | ||
| shortName: string; | ||
| previewUrl: string; | ||
| lastPublished?: string; | ||
| lastUpdated: string; | ||
| timeZone: string; | ||
| customDomains: Array<{ | ||
| id: string; | ||
| url: string; | ||
| lastPublished?: string; | ||
| }>; | ||
| } | ||
| | { success: false; error: string }; | ||
|
|
||
| export type GetSiteCoreInput = { | ||
| siteId: string; | ||
| }; | ||
|
|
||
| export type GetSiteInput = StepInput & | ||
| GetSiteCoreInput & { | ||
| integrationId?: string; | ||
| }; | ||
|
|
||
| async function stepHandler( | ||
| input: GetSiteCoreInput, | ||
| credentials: WebflowCredentials | ||
| ): Promise<GetSiteResult> { | ||
| const apiKey = credentials.WEBFLOW_API_KEY; | ||
|
|
||
| if (!apiKey) { | ||
| return { | ||
| success: false, | ||
| error: | ||
| "WEBFLOW_API_KEY is not configured. Please add it in Project Integrations.", | ||
| }; | ||
| } | ||
|
|
||
| if (!input.siteId) { | ||
| return { | ||
| success: false, | ||
| error: "Site ID is required", | ||
| }; | ||
| } | ||
|
|
||
| try { | ||
| const response = await fetch(`${WEBFLOW_API_URL}/sites/${input.siteId}`, { | ||
bensabic marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| method: "GET", | ||
| headers: { | ||
| Accept: "application/json", | ||
| Authorization: `Bearer ${apiKey}`, | ||
| }, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const errorData = (await response.json()) as { message?: string }; | ||
| return { | ||
| success: false, | ||
| error: errorData.message || `HTTP ${response.status}`, | ||
| }; | ||
| } | ||
|
|
||
| const site = (await response.json()) as WebflowSiteResponse; | ||
|
|
||
| return { | ||
| success: true, | ||
| id: site.id, | ||
| displayName: site.displayName, | ||
| shortName: site.shortName, | ||
| previewUrl: site.previewUrl, | ||
| lastPublished: site.lastPublished, | ||
| lastUpdated: site.lastUpdated, | ||
| timeZone: site.timeZone, | ||
| customDomains: site.customDomains || [], | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| success: false, | ||
| error: `Failed to get site: ${getErrorMessage(error)}`, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| export async function getSiteStep( | ||
| input: GetSiteInput | ||
| ): Promise<GetSiteResult> { | ||
| "use step"; | ||
|
|
||
| const credentials = input.integrationId | ||
| ? await fetchCredentials(input.integrationId) | ||
| : {}; | ||
|
|
||
| return withStepLogging(input, () => stepHandler(input, credentials)); | ||
| } | ||
| getSiteStep.maxRetries = 0; | ||
|
|
||
| export const _integrationType = "webflow"; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import "server-only"; | ||
|
|
||
| import { fetchCredentials } from "@/lib/credential-fetcher"; | ||
| import { type StepInput, withStepLogging } from "@/lib/steps/step-handler"; | ||
| import { getErrorMessage } from "@/lib/utils"; | ||
| import type { WebflowCredentials } from "../credentials"; | ||
|
|
||
| const WEBFLOW_API_URL = "https://api.webflow.com/v2"; | ||
|
|
||
| type WebflowSite = { | ||
| id: string; | ||
| workspaceId: string; | ||
| createdOn: string; | ||
| displayName: string; | ||
| shortName: string; | ||
| lastPublished?: string; | ||
| lastUpdated: string; | ||
| previewUrl: string; | ||
| timeZone: string; | ||
| customDomains?: Array<{ | ||
| id: string; | ||
| url: string; | ||
| lastPublished?: string; | ||
| }>; | ||
| }; | ||
|
|
||
| type ListSitesResult = | ||
| | { | ||
| success: true; | ||
| sites: Array<{ | ||
| id: string; | ||
| displayName: string; | ||
| shortName: string; | ||
| previewUrl: string; | ||
| lastPublished?: string; | ||
| lastUpdated: string; | ||
| customDomains: string[]; | ||
| }>; | ||
| count: number; | ||
| } | ||
| | { success: false; error: string }; | ||
|
|
||
| export type ListSitesCoreInput = Record<string, never>; | ||
|
|
||
| export type ListSitesInput = StepInput & | ||
| ListSitesCoreInput & { | ||
| integrationId?: string; | ||
| }; | ||
|
|
||
| async function stepHandler( | ||
| _input: ListSitesCoreInput, | ||
| credentials: WebflowCredentials | ||
| ): Promise<ListSitesResult> { | ||
| const apiKey = credentials.WEBFLOW_API_KEY; | ||
|
|
||
| if (!apiKey) { | ||
| return { | ||
| success: false, | ||
| error: | ||
| "WEBFLOW_API_KEY is not configured. Please add it in Project Integrations.", | ||
| }; | ||
| } | ||
|
|
||
| try { | ||
| const response = await fetch(`${WEBFLOW_API_URL}/sites`, { | ||
| method: "GET", | ||
| headers: { | ||
| Accept: "application/json", | ||
| Authorization: `Bearer ${apiKey}`, | ||
| }, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const errorData = (await response.json()) as { message?: string }; | ||
| return { | ||
| success: false, | ||
| error: errorData.message || `HTTP ${response.status}`, | ||
| }; | ||
| } | ||
|
|
||
| const data = (await response.json()) as { sites: WebflowSite[] }; | ||
|
|
||
| const sites = data.sites.map((site) => ({ | ||
| id: site.id, | ||
| displayName: site.displayName, | ||
| shortName: site.shortName, | ||
| previewUrl: site.previewUrl, | ||
| lastPublished: site.lastPublished, | ||
| lastUpdated: site.lastUpdated, | ||
| customDomains: site.customDomains?.map((d) => d.url) || [], | ||
| })); | ||
|
|
||
| return { | ||
| success: true, | ||
| sites, | ||
| count: sites.length, | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| success: false, | ||
| error: `Failed to list sites: ${getErrorMessage(error)}`, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| export async function listSitesStep( | ||
| input: ListSitesInput | ||
| ): Promise<ListSitesResult> { | ||
| "use step"; | ||
|
|
||
| const credentials = input.integrationId | ||
| ? await fetchCredentials(input.integrationId) | ||
| : {}; | ||
|
|
||
| return withStepLogging(input, () => stepHandler(input, credentials)); | ||
| } | ||
| listSitesStep.maxRetries = 0; | ||
|
|
||
| export const _integrationType = "webflow"; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.