chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+476
View File
@@ -0,0 +1,476 @@
// Auto-generated file - do not edit manually
// Generated by scripts/generate-docs.ts
// Maps block types to their icon component references for the integrations page
import type { ComponentType, SVGProps } from 'react'
import {
AgentMailIcon,
AgentPhoneIcon,
AgiloftIcon,
AhrefsIcon,
AirtableIcon,
AirweaveIcon,
AlgoliaIcon,
AmplitudeIcon,
ApifyIcon,
ApolloIcon,
AppConfigIcon,
ArxivIcon,
AsanaIcon,
AshbyIcon,
AthenaIcon,
AttioIcon,
AzureIcon,
BoxCompanyIcon,
BrainIcon,
BrandfetchIcon,
BrexIcon,
BrightDataIcon,
BrowserUseIcon,
CalComIcon,
CalendlyIcon,
CirclebackIcon,
ClayIcon,
ClerkIcon,
ClickHouseIcon,
CloudFormationIcon,
CloudflareIcon,
CloudWatchIcon,
CodePipelineIcon,
ConfluenceIcon,
ContextDevIcon,
ConvexIcon,
CrowdStrikeIcon,
CursorIcon,
DagsterIcon,
DatabricksIcon,
DatadogIcon,
DatagmaIcon,
DaytonaIcon,
DevinIcon,
DiscordIcon,
DocumentIcon,
DocuSignIcon,
DowndetectorIcon,
DropboxIcon,
DropcontactIcon,
DsPyIcon,
DubIcon,
DuckDuckGoIcon,
DynamoDBIcon,
ElasticsearchIcon,
ElevenLabsIcon,
EmailBisonIcon,
EnrichmentIcon,
EnrichSoIcon,
EnrowIcon,
EvernoteIcon,
ExaAIIcon,
ExtendIcon,
FathomIcon,
FindymailIcon,
FirecrawlIcon,
FirefliesIcon,
GammaIcon,
GithubIcon,
GitLabIcon,
GmailIcon,
GongIcon,
GoogleAdsIcon,
GoogleAppsheetIcon,
GoogleBigQueryIcon,
GoogleBooksIcon,
GoogleCalendarIcon,
GoogleContactsIcon,
GoogleDocsIcon,
GoogleDriveIcon,
GoogleFormsIcon,
GoogleGroupsIcon,
GoogleIcon,
GoogleMapsIcon,
GoogleMeetIcon,
GooglePagespeedIcon,
GoogleSheetsIcon,
GoogleSlidesIcon,
GoogleTasksIcon,
GoogleTranslateIcon,
GoogleVaultIcon,
GrafanaIcon,
GrainIcon,
GranolaIcon,
GreenhouseIcon,
GreptileIcon,
HexIcon,
HubspotIcon,
HuggingFaceIcon,
HunterIOIcon,
IAMIcon,
IcypeasIcon,
IdentityCenterIcon,
IncidentioIcon,
InfisicalIcon,
InstantlyIcon,
IntercomIcon,
JinaAIIcon,
JiraIcon,
JiraServiceManagementIcon,
JupyterIcon,
KalshiIcon,
KetchIcon,
LangsmithIcon,
LatexIcon,
LaunchDarklyIcon,
LeadMagicIcon,
LemlistIcon,
LinearIcon,
LinkedInIcon,
LinkupIcon,
LinqIcon,
LoopsIcon,
LumaIcon,
MailchimpIcon,
MailgunIcon,
MailServerIcon,
Mem0Icon,
MicrosoftDataverseIcon,
MicrosoftExcelIcon,
MicrosoftOneDriveIcon,
MicrosoftPlannerIcon,
MicrosoftSharepointIcon,
MicrosoftTeamsIcon,
MillionVerifierIcon,
MistralIcon,
MondayIcon,
MongoDBIcon,
MySQLIcon,
Neo4jIcon,
NeverBounceIcon,
NewRelicIcon,
NotionIcon,
ObsidianIcon,
OktaIcon,
OnePasswordIcon,
OpenAIIcon,
OutlookIcon,
PackageSearchIcon,
PagerDutyIcon,
ParallelIcon,
PeopleDataLabsIcon,
PerplexityIcon,
PersonaIcon,
PineconeIcon,
PipedriveIcon,
PolymarketIcon,
PostgresIcon,
PosthogIcon,
ProfoundIcon,
ProspeoIcon,
PulseIcon,
QdrantIcon,
QuartrIcon,
QuiverIcon,
RailwayIcon,
RB2BIcon,
RDSIcon,
RedditIcon,
RedisIcon,
ReductoIcon,
ResendIcon,
RevenueCatIcon,
RipplingIcon,
RootlyIcon,
S3Icon,
SalesforceIcon,
SapConcurIcon,
SapS4HanaIcon,
SESIcon,
SecretsManagerIcon,
SendblueIcon,
SendgridIcon,
SentryIcon,
SerperIcon,
ServiceNowIcon,
SftpIcon,
ShopifyIcon,
SimilarwebIcon,
SimTriggerIcon,
SixtyfourIcon,
SlackIcon,
SmtpIcon,
SportmonksIcon,
SQSIcon,
SquareIcon,
SshIcon,
STSIcon,
STTIcon,
StagehandIcon,
StripeIcon,
SupabaseIcon,
TailscaleIcon,
TavilyIcon,
TelegramIcon,
TemporalIcon,
TextractIcon,
ThriveIcon,
TinybirdIcon,
TrelloIcon,
TriggerDevIcon,
TwilioIcon,
TypeformIcon,
UpstashIcon,
UptimeRobotIcon,
VantaIcon,
VercelIcon,
WealthboxIcon,
WebflowIcon,
WhatsAppIcon,
WikipediaIcon,
WizaIcon,
WordpressIcon,
WorkdayIcon,
xIcon,
YouTubeIcon,
ZendeskIcon,
ZepIcon,
ZeroBounceIcon,
ZoomIcon,
ZoomInfoIcon,
} from '@/components/icons'
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
export const blockTypeToIconMap: Record<string, IconComponent> = {
agentmail: AgentMailIcon,
agentphone: AgentPhoneIcon,
agiloft: AgiloftIcon,
ahrefs: AhrefsIcon,
airtable: AirtableIcon,
airweave: AirweaveIcon,
algolia: AlgoliaIcon,
amplitude: AmplitudeIcon,
apify: ApifyIcon,
apollo: ApolloIcon,
appconfig: AppConfigIcon,
arxiv: ArxivIcon,
asana: AsanaIcon,
ashby: AshbyIcon,
athena: AthenaIcon,
attio: AttioIcon,
azure_devops: AzureIcon,
box: BoxCompanyIcon,
brandfetch: BrandfetchIcon,
brex: BrexIcon,
brightdata: BrightDataIcon,
browser_use: BrowserUseIcon,
calcom: CalComIcon,
calendly: CalendlyIcon,
circleback: CirclebackIcon,
clay: ClayIcon,
clerk: ClerkIcon,
clickhouse: ClickHouseIcon,
cloudflare: CloudflareIcon,
cloudformation: CloudFormationIcon,
cloudwatch: CloudWatchIcon,
codepipeline: CodePipelineIcon,
confluence_v2: ConfluenceIcon,
context_dev: ContextDevIcon,
convex: ConvexIcon,
crowdstrike: CrowdStrikeIcon,
cursor_v2: CursorIcon,
dagster: DagsterIcon,
databricks: DatabricksIcon,
datadog: DatadogIcon,
datagma: DatagmaIcon,
daytona: DaytonaIcon,
devin: DevinIcon,
discord: DiscordIcon,
docusign: DocuSignIcon,
downdetector: DowndetectorIcon,
dropbox: DropboxIcon,
dropcontact: DropcontactIcon,
dspy: DsPyIcon,
dub: DubIcon,
duckduckgo: DuckDuckGoIcon,
dynamodb: DynamoDBIcon,
elasticsearch: ElasticsearchIcon,
elevenlabs: ElevenLabsIcon,
emailbison: EmailBisonIcon,
enrich: EnrichSoIcon,
enrichment: EnrichmentIcon,
enrow: EnrowIcon,
evernote: EvernoteIcon,
exa: ExaAIIcon,
extend_v2: ExtendIcon,
fathom: FathomIcon,
file_v5: DocumentIcon,
findymail: FindymailIcon,
firecrawl: FirecrawlIcon,
fireflies_v2: FirefliesIcon,
gamma: GammaIcon,
github_v2: GithubIcon,
gitlab: GitLabIcon,
gmail_v2: GmailIcon,
gong: GongIcon,
google_ads: GoogleAdsIcon,
google_appsheet: GoogleAppsheetIcon,
google_bigquery: GoogleBigQueryIcon,
google_books: GoogleBooksIcon,
google_calendar_v2: GoogleCalendarIcon,
google_contacts: GoogleContactsIcon,
google_docs: GoogleDocsIcon,
google_drive: GoogleDriveIcon,
google_forms: GoogleFormsIcon,
google_groups: GoogleGroupsIcon,
google_maps: GoogleMapsIcon,
google_meet: GoogleMeetIcon,
google_pagespeed: GooglePagespeedIcon,
google_search: GoogleIcon,
google_sheets_v2: GoogleSheetsIcon,
google_slides_v2: GoogleSlidesIcon,
google_tasks: GoogleTasksIcon,
google_translate: GoogleTranslateIcon,
google_vault: GoogleVaultIcon,
grafana: GrafanaIcon,
grain: GrainIcon,
granola: GranolaIcon,
greenhouse: GreenhouseIcon,
greptile: GreptileIcon,
hex: HexIcon,
hubspot: HubspotIcon,
huggingface: HuggingFaceIcon,
hunter: HunterIOIcon,
iam: IAMIcon,
icypeas: IcypeasIcon,
identity_center: IdentityCenterIcon,
imap: MailServerIcon,
incidentio: IncidentioIcon,
infisical: InfisicalIcon,
instantly: InstantlyIcon,
intercom_v2: IntercomIcon,
jina: JinaAIIcon,
jira: JiraIcon,
jira_service_management: JiraServiceManagementIcon,
jupyter: JupyterIcon,
kalshi_v2: KalshiIcon,
ketch: KetchIcon,
knowledge: PackageSearchIcon,
langsmith: LangsmithIcon,
latex: LatexIcon,
launchdarkly: LaunchDarklyIcon,
leadmagic: LeadMagicIcon,
lemlist: LemlistIcon,
linear_v2: LinearIcon,
linkedin: LinkedInIcon,
linkup: LinkupIcon,
linq: LinqIcon,
loops: LoopsIcon,
luma: LumaIcon,
mailchimp: MailchimpIcon,
mailgun: MailgunIcon,
mem0: Mem0Icon,
memory: BrainIcon,
microsoft_ad: AzureIcon,
microsoft_dataverse: MicrosoftDataverseIcon,
microsoft_excel_v2: MicrosoftExcelIcon,
microsoft_planner: MicrosoftPlannerIcon,
microsoft_teams: MicrosoftTeamsIcon,
millionverifier: MillionVerifierIcon,
mistral_parse_v3: MistralIcon,
monday: MondayIcon,
mongodb: MongoDBIcon,
mysql: MySQLIcon,
neo4j: Neo4jIcon,
neverbounce: NeverBounceIcon,
new_relic: NewRelicIcon,
notion_v2: NotionIcon,
obsidian: ObsidianIcon,
okta: OktaIcon,
onedrive: MicrosoftOneDriveIcon,
onepassword: OnePasswordIcon,
openai: OpenAIIcon,
outlook: OutlookIcon,
pagerduty: PagerDutyIcon,
parallel_ai: ParallelIcon,
peopledatalabs: PeopleDataLabsIcon,
perplexity: PerplexityIcon,
persona: PersonaIcon,
pinecone: PineconeIcon,
pipedrive: PipedriveIcon,
polymarket: PolymarketIcon,
postgresql: PostgresIcon,
posthog: PosthogIcon,
profound: ProfoundIcon,
prospeo: ProspeoIcon,
pulse_v2: PulseIcon,
qdrant: QdrantIcon,
quartr: QuartrIcon,
quiver: QuiverIcon,
railway: RailwayIcon,
rb2b: RB2BIcon,
rds: RDSIcon,
reddit: RedditIcon,
redis: RedisIcon,
reducto_v2: ReductoIcon,
resend: ResendIcon,
revenuecat: RevenueCatIcon,
rippling: RipplingIcon,
rootly: RootlyIcon,
s3: S3Icon,
salesforce: SalesforceIcon,
sap_concur: SapConcurIcon,
sap_s4hana: SapS4HanaIcon,
secrets_manager: SecretsManagerIcon,
sendblue: SendblueIcon,
sendgrid: SendgridIcon,
sentry: SentryIcon,
serper: SerperIcon,
servicenow: ServiceNowIcon,
ses: SESIcon,
sftp: SftpIcon,
sharepoint_v2: MicrosoftSharepointIcon,
shopify: ShopifyIcon,
sim_workspace_event: SimTriggerIcon,
similarweb: SimilarwebIcon,
sixtyfour: SixtyfourIcon,
slack: SlackIcon,
smtp: SmtpIcon,
sportmonks: SportmonksIcon,
sqs: SQSIcon,
square: SquareIcon,
ssh: SshIcon,
stagehand: StagehandIcon,
stripe: StripeIcon,
sts: STSIcon,
stt_v2: STTIcon,
supabase: SupabaseIcon,
tailscale: TailscaleIcon,
tavily: TavilyIcon,
telegram: TelegramIcon,
temporal: TemporalIcon,
textract_v2: TextractIcon,
thrive: ThriveIcon,
tinybird: TinybirdIcon,
trello: TrelloIcon,
trigger_dev: TriggerDevIcon,
twilio_sms: TwilioIcon,
twilio_voice: TwilioIcon,
typeform: TypeformIcon,
upstash: UpstashIcon,
uptimerobot: UptimeRobotIcon,
vanta: VantaIcon,
vercel: VercelIcon,
wealthbox: WealthboxIcon,
webflow: WebflowIcon,
whatsapp: WhatsAppIcon,
wikipedia: WikipediaIcon,
wiza: WizaIcon,
wordpress: WordpressIcon,
workday: WorkdayIcon,
x: xIcon,
youtube: YouTubeIcon,
zendesk: ZendeskIcon,
zep: ZepIcon,
zerobounce: ZeroBounceIcon,
zoom: ZoomIcon,
zoominfo: ZoomInfoIcon,
}
+79
View File
@@ -0,0 +1,79 @@
/**
* Canonical entry point for everything integrations-related. Landing
* `/integrations`, workspace integrations, and the sitemap all import from
* here so the data shape and helpers stay in lockstep.
*
* `INTEGRATIONS` is the serialized projection of `BlockConfig` written by
* `scripts/generate-docs.ts` whenever a block changes.
*
* `POPULAR_WORKFLOWS` is derived from each block's `*BlockMeta` export (see
* `apps/sim/blocks/registry.ts`, which now hosts both the execution
* `BlockConfig` lookups and the presentation `BlockMeta` lookups). Block
* files are the source of truth for both surfaces.
*/
import { stripVersionSuffix } from '@sim/utils/string'
import integrationsJson from '@/lib/integrations/integrations.json'
import type { Integration } from '@/lib/integrations/types'
import { getAllBlockMeta } from '@/blocks/registry'
/** All integrations surfaced in the catalog, ordered by `scripts/generate-docs.ts`. */
export const INTEGRATIONS: readonly Integration[] =
integrationsJson.integrations as readonly Integration[]
/**
* ISO date of the last real catalog change, stamped by `scripts/generate-docs.ts`.
* Drives sitemap `lastModified`, JSON-LD `dateModified`, and the visible
* last-updated line on integration pages.
*/
export const INTEGRATIONS_UPDATED_AT: string = integrationsJson.updatedAt
/** A curated `from → to` block-pair workflow surfaced on the landing page. */
export interface PopularWorkflow {
/** Integration display name (matches `Integration.name`). */
from: string
/** Integration display name. */
to: string
headline: string
description: string
}
const TYPE_TO_NAME = new Map<string, string>()
for (const integration of INTEGRATIONS) {
TYPE_TO_NAME.set(integration.type, integration.name)
TYPE_TO_NAME.set(stripVersionSuffix(integration.type), integration.name)
}
/**
* Curated popular workflow pairs (templates flagged `featured: true` that
* reference at least one other integration). Derived from per-block meta —
* each entry's `from` is the owner block, `to` is the first
* `alsoIntegrations` entry, and `headline`/`description` come from the
* template title and prompt.
*/
export const POPULAR_WORKFLOWS: readonly PopularWorkflow[] = (() => {
const pairs: PopularWorkflow[] = []
for (const [ownerType, meta] of Object.entries(getAllBlockMeta())) {
for (const template of meta.templates ?? []) {
if (!template.featured) continue
const toType = template.alsoIntegrations?.[0]
if (!toType) continue
const from = TYPE_TO_NAME.get(ownerType) ?? TYPE_TO_NAME.get(stripVersionSuffix(ownerType))
const to = TYPE_TO_NAME.get(toType) ?? TYPE_TO_NAME.get(stripVersionSuffix(toType))
if (!from || !to) continue
pairs.push({ from, to, headline: template.title, description: template.prompt })
}
}
return pairs
})()
export { blockTypeToIconMap } from '@/lib/integrations/icon-mapping'
export {
type OAuthServiceMatch,
resolveOAuthServiceForIntegration,
resolveOAuthServiceForSlug,
} from '@/lib/integrations/oauth-service'
export type { AuthType, FAQItem, Integration } from '@/lib/integrations/types'
export { getAllBlockMeta, getBlockMeta, getTemplatesForBlock } from '@/blocks/registry'
export type { BlockMeta, BlockTemplate } from '@/blocks/types'
export { formatIntegrationType } from '@/blocks/types'
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
/**
* Hand-authored, integration-specific landing content, keyed by integration
* slug. This is a pure-data generation input: `scripts/generate-docs.ts` reads
* it and bakes the matching entry into `integrations.json`, so the landing page
* consumes a single source (`integration.landingContent`) with no render-time
* augmentation. Has no app imports so the build script can import it safely.
*/
import type { IntegrationLandingContent } from '@/app/(landing)/integrations/data/types'
export const INTEGRATION_LANDING_CONTENT: Record<string, IntegrationLandingContent> = {
slack: {
install: {
heading: 'Add Sim to your Slack workspace',
intro:
'Sim connects to Slack through Slacks official OAuth flow. The “Add to Slack” button lives inside your Sim account (after sign-in) — connect from there and the Sim bot is installed in your Slack workspace. The steps below show exactly how to reach it.',
steps: [
{
title: 'Create your free Sim account',
body: 'Sign up at sim.ai — no credit card required.',
},
{
title: 'Add a Slack block',
body: 'Open a workflow, drag in a Slack block, and open its credential dropdown.',
},
{
title: 'Connect Slack',
body: 'Click Connect Slack, choose your workspace, and approve the requested permissions. This installs the Sim bot in your Slack workspace.',
},
{
title: 'Invite the bot and build',
body: 'Invite the Sim bot to the channels it should act in, pick a Slack action, wire it into your agent, and run.',
},
],
},
privacy: {
body: 'Sim requests only the Slack permissions its actions and triggers need, and never shows private channel names or messages to people who are not members of those channels in Slack.',
href: '/privacy',
},
},
}
@@ -0,0 +1,131 @@
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import integrationsJson from '@/lib/integrations/integrations.json'
import { resolveOAuthServiceForSlug } from '@/lib/integrations/oauth-service'
import type { Integration } from '@/lib/integrations/types'
const INTEGRATIONS = integrationsJson.integrations as readonly Integration[]
/**
* Pinned slug → OAuth providerId mapping for every OAuth integration in the
* catalog. Guards against silent drift between block `serviceId`s, the
* generated catalog, and `OAUTH_PROVIDERS` — the failure mode that made
* Jira Service Management, Google Slides, and Monday fall back to the
* API-key connect path.
*/
const EXPECTED_PROVIDER_BY_SLUG: Record<string, string> = {
airtable: 'airtable',
asana: 'asana',
attio: 'attio',
'azure-ad': 'microsoft-ad',
box: 'box',
'cal-com': 'calcom',
confluence: 'confluence',
docusign: 'docusign',
dropbox: 'dropbox',
gmail: 'google-email',
'google-ads': 'google-ads',
'google-bigquery': 'google-bigquery',
'google-calendar': 'google-calendar',
'google-contacts': 'google-contacts',
'google-docs': 'google-docs',
'google-drive': 'google-drive',
'google-forms': 'google-forms',
'google-groups': 'google-groups',
'google-meet': 'google-meet',
'google-sheets': 'google-sheets',
'google-slides': 'google-drive',
'google-tasks': 'google-tasks',
'google-vault': 'google-vault',
hubspot: 'hubspot',
jira: 'jira',
'jira-service-management': 'jira',
linear: 'linear',
linkedin: 'linkedin',
'microsoft-dataverse': 'microsoft-dataverse',
'microsoft-excel': 'microsoft-excel',
'microsoft-planner': 'microsoft-planner',
'microsoft-teams': 'microsoft-teams',
monday: 'monday',
notion: 'notion',
onedrive: 'onedrive',
outlook: 'outlook',
pipedrive: 'pipedrive',
reddit: 'reddit',
salesforce: 'salesforce',
sharepoint: 'sharepoint',
shopify: 'shopify',
slack: 'slack',
trello: 'trello',
wealthbox: 'wealthbox',
webflow: 'webflow',
wordpress: 'wordpress',
x: 'x',
zoom: 'zoom',
}
describe('resolveOAuthServiceForSlug', () => {
it.concurrent('resolves integrations whose name differs from the OAuth service name', () => {
const jsm = resolveOAuthServiceForSlug('jira-service-management')
expect(jsm?.providerId).toBe('jira')
expect(jsm?.serviceName).toBe('Jira')
const slides = resolveOAuthServiceForSlug('google-slides')
expect(slides?.providerId).toBe('google-drive')
const monday = resolveOAuthServiceForSlug('monday')
expect(monday?.providerId).toBe('monday')
})
it.concurrent('resolves integrations whose name matches the OAuth service name', () => {
const jira = resolveOAuthServiceForSlug('jira')
expect(jira?.providerId).toBe('jira')
expect(jira?.serviceName).toBe('Jira')
const gmail = resolveOAuthServiceForSlug('gmail')
expect(gmail?.providerId).toBe('google-email')
})
it.concurrent('returns null for unknown slugs', () => {
expect(resolveOAuthServiceForSlug('not-a-real-integration')).toBeNull()
})
it.concurrent('returns null for non-OAuth integrations', () => {
const apiKeyIntegration = INTEGRATIONS.find((entry) => entry.authType === 'api-key')
expect(apiKeyIntegration).toBeDefined()
expect(resolveOAuthServiceForSlug(apiKeyIntegration!.slug)).toBeNull()
})
it.concurrent('resolves every OAuth integration in the catalog', () => {
const oauthIntegrations = INTEGRATIONS.filter((entry) => entry.authType === 'oauth')
expect(oauthIntegrations.length).toBeGreaterThan(0)
const unresolved = oauthIntegrations
.filter((entry) => resolveOAuthServiceForSlug(entry.slug) === null)
.map((entry) => entry.slug)
expect(unresolved).toEqual([])
})
it.concurrent('resolves the pinned provider for every enumerated OAuth integration', () => {
const resolved = Object.fromEntries(
Object.keys(EXPECTED_PROVIDER_BY_SLUG).map((slug) => [
slug,
resolveOAuthServiceForSlug(slug)?.providerId ?? null,
])
)
expect(resolved).toEqual(EXPECTED_PROVIDER_BY_SLUG)
})
it.concurrent('carries oauthServiceId for exactly the OAuth catalog entries', () => {
const missing = INTEGRATIONS.filter(
(entry) => entry.authType === 'oauth' && !entry.oauthServiceId
).map((entry) => entry.slug)
const unexpected = INTEGRATIONS.filter(
(entry) => entry.authType !== 'oauth' && entry.oauthServiceId
).map((entry) => entry.slug)
expect(missing).toEqual([])
expect(unexpected).toEqual([])
})
})
@@ -0,0 +1,79 @@
import type { ComponentType } from 'react'
import integrationsJson from '@/lib/integrations/integrations.json'
import type { Integration } from '@/lib/integrations/types'
import { getServiceConfigByServiceId } from '@/lib/oauth'
import { SLACK_CUSTOM_BOT_PROVIDER_ID } from '@/lib/oauth/types'
import type { ServiceAccountProviderId } from '@/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal'
const INTEGRATIONS_DATA: readonly Integration[] =
integrationsJson.integrations as readonly Integration[]
/**
* Shape returned from resolving an integration to its OAuth service entry in
* `OAUTH_PROVIDERS`. Carries the metadata needed to mount `ConnectOAuthModal`
* (and optionally `ConnectServiceAccountModal`) for the integration.
*/
export interface OAuthServiceMatch {
providerId: string
requiredScopes: string[]
serviceName: string
serviceIcon: ComponentType<{ className?: string }>
/**
* When set, the matched OAuth service also exposes a service-account flow
* (e.g. Google services with `google-service-account`, Atlassian services
* with `atlassian-service-account`). Callers may surface this as a secondary
* connect option.
*/
serviceAccountProviderId?: ServiceAccountProviderId
}
/**
* Narrows the runtime `OAuthServiceConfig.serviceAccountProviderId` string to
* the {@link ServiceAccountProviderId} union. Anything outside the union is
* unsupported by `ConnectServiceAccountModal` and is silently ignored.
*/
function asServiceAccountProviderId(
value: string | undefined
): ServiceAccountProviderId | undefined {
if (
value === 'google-service-account' ||
value === 'atlassian-service-account' ||
value === SLACK_CUSTOM_BOT_PROVIDER_ID
) {
return value
}
return undefined
}
/**
* Looks up the OAuth service entry registered under the integration's
* `oauthServiceId` — the service id its block declares on the `oauth-input`
* subBlock, carried into the catalog at generation time. Returns `null` for
* non-OAuth integrations or when no matching service is registered in
* `OAUTH_PROVIDERS`.
*/
export function resolveOAuthServiceForIntegration(
integration: Integration
): OAuthServiceMatch | null {
if (integration.authType !== 'oauth' || !integration.oauthServiceId) return null
const service = getServiceConfigByServiceId(integration.oauthServiceId)
if (!service) return null
return {
providerId: service.providerId,
requiredScopes: service.scopes ?? [],
serviceName: service.name,
serviceIcon: service.icon as ComponentType<{ className?: string }>,
serviceAccountProviderId: asServiceAccountProviderId(service.serviceAccountProviderId),
}
}
/**
* Resolves the integration entry for a catalog slug, then derives its OAuth
* service match. Returns `null` when the slug is unknown or the matching
* integration is not an OAuth integration.
*/
export function resolveOAuthServiceForSlug(slug: string): OAuthServiceMatch | null {
const integration = INTEGRATIONS_DATA.find((entry) => entry.slug === slug)
if (!integration) return null
return resolveOAuthServiceForIntegration(integration)
}
+68
View File
@@ -0,0 +1,68 @@
/**
* Shared types for the integrations catalog. Mirrors the JSON shape written by
* `scripts/generate-docs.ts` → `writeIntegrationsJson()`, which is the
* serialized projection of `BlockConfig` consumed by landing + workspace UIs.
*/
import type { IntegrationLandingContent } from '@/app/(landing)/integrations/data/types'
import type { BlockConfig, IntegrationTag } from '@/blocks/types'
/** Normalized authentication mode surfaced in the catalog. */
export type AuthType = 'oauth' | 'api-key' | 'none'
/** Trigger entry enriched from the trigger registry at generation time. */
interface TriggerInfo {
id: string
name: string
description: string
}
/** Operation entry enriched from the tool registry at generation time. */
interface OperationInfo {
name: string
description: string
}
/** Single FAQ item rendered on a per-integration landing page. */
export interface FAQItem {
question: string
answer: string
}
/**
* Catalog projection of a `BlockConfig`. Direct `BlockConfig` fields are
* referenced via indexed access so the two stay in lockstep; the remaining
* fields are generation-time enrichments (see `scripts/generate-docs.ts`).
*/
export interface Integration {
type: BlockConfig['type']
name: BlockConfig['name']
description: BlockConfig['description']
longDescription: NonNullable<BlockConfig['longDescription']>
category: BlockConfig['category']
integrationType: NonNullable<BlockConfig['integrationType']>
bgColor: BlockConfig['bgColor']
/** Tags sourced from the block's `*BlockMeta` export at generation time. */
tags?: IntegrationTag[]
/** URL slug derived from `name`. */
slug: string
/** Name of the React icon component (resolved client-side via `blockTypeToIconMap`). */
iconName: string
/** Canonical docs URL for the integration. */
docsUrl: string
/** Operations enriched with descriptions from the tool registry. */
operations: OperationInfo[]
operationCount: number
/** Triggers enriched with details from the trigger registry. */
triggers: TriggerInfo[]
triggerCount: number
/** Authentication mode inferred from `BlockConfig.subBlocks`. */
authType: AuthType
/**
* OAuth service id from the block's `oauth-input` subBlock (a service key in
* `OAUTH_PROVIDERS`). Present exactly when `authType` is `'oauth'`.
*/
oauthServiceId?: string
/** Hand-authored landing content baked in at generation time (see `landing-content.ts`). */
landingContent?: IntegrationLandingContent
}