diff --git a/.claude/skills/sdk-docs-writing/SKILL.md b/.claude/skills/sdk-docs-writing/SKILL.md index 7a50564..6f067e0 100644 --- a/.claude/skills/sdk-docs-writing/SKILL.md +++ b/.claude/skills/sdk-docs-writing/SKILL.md @@ -174,6 +174,7 @@ const records = await base44.entities.MyEntity.list(); - **Avoid gerunds** in section headings within JSDoc. Prefer imperatives or noun phrases. - **State environment constraints** when a method is browser-only: "Requires a browser environment and can't be used in the backend." - **Document side effects** explicitly (e.g., "automatically sets the token for subsequent requests"). +- **Link method references.** When mentioning another SDK method or module by name in JSDoc prose, always use `{@link}` or `{@linkcode}` to create a cross-reference. Never leave a method name as plain text when a link target exists. ## Doc generation pipeline @@ -196,6 +197,10 @@ mint dev When adding a new public type, add it to `types-to-expose.json`. When a helper type should live inside another page, add it to `appended-articles.json`. +## Review checklist for multi-page changes + +When a docs task touches three or more pages in `mintlify-docs` (including pages regenerated by `create-docs-local`), create a `REVIEW-CHECKLIST.md` file in the mintlify-docs repo root listing every affected page with its URL path and what to verify. Split the list into intentional changes and side-effect changes from regeneration. Add a reminder to delete the file before committing. Tell the user the checklist exists so they can work through it at their own pace. + ## Checklist before submitting a PR 1. **JSDoc completeness:** Every public method has description, `@param`, `@returns`, and `@example`. diff --git a/scripts/mintlify-post-processing/appended-articles.json b/scripts/mintlify-post-processing/appended-articles.json index cb83dff..72a82d6 100644 --- a/scripts/mintlify-post-processing/appended-articles.json +++ b/scripts/mintlify-post-processing/appended-articles.json @@ -1,4 +1,8 @@ { + "interfaces/ConnectorsModule": [ + "type-aliases/ConnectorIntegrationType", + "interfaces/ConnectorIntegrationTypeRegistry" + ], "type-aliases/EntitiesModule": [ "interfaces/EntityHandler", "type-aliases/EntityRecord", diff --git a/scripts/mintlify-post-processing/copy-to-local-docs.js b/scripts/mintlify-post-processing/copy-to-local-docs.js index a525159..6e6b87f 100644 --- a/scripts/mintlify-post-processing/copy-to-local-docs.js +++ b/scripts/mintlify-post-processing/copy-to-local-docs.js @@ -160,7 +160,8 @@ function updateDocsJson(repoDir, sdkFiles) { `SDK Reference pages: ${JSON.stringify(sdkReferencePages, null, 2)}` ); - // Navigate to: Developers tab -> anchors -> SDK anchor -> groups -> SDK Reference + // Navigate to: Developers tab -> SDK section -> groups -> SDK Reference + // Supports both legacy "anchors" format and current "dropdowns" format. const developersTab = docs.navigation.tabs.find( (tab) => tab.tab === "Developers" ); @@ -170,13 +171,13 @@ function updateDocsJson(repoDir, sdkFiles) { process.exit(1); } - // Find the SDK anchor - const sdkAnchor = developersTab.anchors?.find( - (anchor) => anchor.anchor === "SDK" - ); + // Find the SDK section (try dropdowns first, then fall back to anchors) + const sdkAnchor = + developersTab.dropdowns?.find((d) => d.dropdown === "SDK") ?? + developersTab.anchors?.find((a) => a.anchor === "SDK"); if (!sdkAnchor) { - console.error("Could not find 'SDK' anchor in Developers tab"); + console.error("Could not find 'SDK' dropdown or anchor in Developers tab"); process.exit(1); } diff --git a/scripts/mintlify-post-processing/file-processing/file-processing.js b/scripts/mintlify-post-processing/file-processing/file-processing.js index 3258237..4589768 100755 --- a/scripts/mintlify-post-processing/file-processing/file-processing.js +++ b/scripts/mintlify-post-processing/file-processing/file-processing.js @@ -1017,6 +1017,123 @@ function applySignatureCleanup(dir) { } } +/** + * Transforms deprecated method sections: + * 1. Removes ~~strikethrough~~ and prepends a warning emoji to the heading + * 2. Extracts the #### Deprecated block, removes it, and re-inserts it as a + * callout between the heading and the signature blockquote + */ +function processDeprecatedMethods(content) { + const lines = content.split("\n"); + let modified = false; + let inFence = false; + + // First pass: find all deprecated sections and collect their info + const deprecatedSections = []; + for (let i = 0; i < lines.length; i++) { + const trimmed = lines[i].trim(); + if (trimmed.startsWith("```")) { + inFence = !inFence; + continue; + } + if (inFence) continue; + + if (trimmed === "#### Deprecated") { + const start = i; + let end = i + 1; + let message = ""; + // The first non-empty line after "#### Deprecated" is the deprecation message. + // Any subsequent lines are description text that TypeDoc misplaced here. + while (end < lines.length) { + const t = lines[end].trim(); + if (t.startsWith("#### ") || t.startsWith("### ") || t.startsWith("## ") || t === "***") break; + if (!message && t) { + message = t; + } + end++; + } + deprecatedSections.push({ start, end, message }); + } + } + + if (deprecatedSections.length === 0) { + return { content, modified: false }; + } + + // Remove deprecated sections bottom-up so indices stay valid + for (let s = deprecatedSections.length - 1; s >= 0; s--) { + const { start, end } = deprecatedSections[s]; + lines.splice(start, end - start); + modified = true; + } + + // Second pass: transform headings and insert Danger callouts + inFence = false; + for (let i = 0; i < lines.length; i++) { + const trimmed = lines[i].trim(); + if (trimmed.startsWith("```")) { + inFence = !inFence; + continue; + } + if (inFence) continue; + + // Match deprecated heading: ### ~~methodName()~~ (TypeDoc wraps deprecated names in ~~) + const headingMatch = trimmed.match(/^###\s+~~(.+?)~~\s*$/); + if (!headingMatch) continue; + + // Remove strikethrough and prepend warning emoji + lines[i] = lines[i].replace( + /^(###\s+)~~(.+?)~~\s*$/, + "$1\u26A0\uFE0F $2" + ); + modified = true; + + // Find the matching deprecated message by method name + const methodName = headingMatch[1].replace(/\(\)$/, ""); + const section = deprecatedSections.find((sec) => + sec.message.toLowerCase().includes(methodName.toLowerCase()) || + deprecatedSections.length === 1 + ) || deprecatedSections.shift(); + + if (!section || !section.message) continue; + + // Insert Danger callout right after the heading (before the signature) + const dangerBlock = [ + "", + "", + `**Deprecated:** ${section.message}`, + "", + "", + ]; + lines.splice(i + 1, 0, ...dangerBlock); + } + + return { content: lines.join("\n"), modified }; +} + +function applyDeprecatedMethodProcessing(dir) { + if (!fs.existsSync(dir)) return; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + applyDeprecatedMethodProcessing(entryPath); + } else if ( + entry.isFile() && + (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) + ) { + const content = fs.readFileSync(entryPath, "utf-8"); + const { content: updated, modified } = processDeprecatedMethods(content); + if (modified) { + fs.writeFileSync(entryPath, updated, "utf-8"); + console.log( + `Processed deprecated methods: ${path.relative(DOCS_DIR, entryPath)}` + ); + } + } + } +} + function demoteNonCallableHeadings(content) { const lines = content.split("\n"); let inFence = false; @@ -1286,6 +1403,11 @@ function groupTypeDefinitions(content) { // Define type definition patterns for different modules const typeGroups = [ + // Connectors module + { + types: ["ConnectorIntegrationType", "ConnectorIntegrationTypeRegistry"], + indicator: "ConnectorIntegrationType" + }, // Entities module { types: ["EntityRecord", "EntityTypeRegistry", "SortField"], @@ -1914,6 +2036,9 @@ function main() { // Clean up signatures: fix truncated generics, simplify keyof constraints, break long lines applySignatureCleanup(DOCS_DIR); + // Transform deprecated methods: add badge to heading, move deprecation notice to Warning callout + applyDeprecatedMethodProcessing(DOCS_DIR); + applyHeadingDemotion(DOCS_DIR); // Group intro sections (Built-in User Entity, Generated Types, etc.) under Overview diff --git a/scripts/mintlify-post-processing/types-to-expose.json b/scripts/mintlify-post-processing/types-to-expose.json index 20229cf..380ba19 100644 --- a/scripts/mintlify-post-processing/types-to-expose.json +++ b/scripts/mintlify-post-processing/types-to-expose.json @@ -5,6 +5,8 @@ "AnalyticsModule", "AppLogsModule", "AuthModule", + "ConnectorIntegrationType", + "ConnectorIntegrationTypeRegistry", "ConnectorsModule", "CustomIntegrationsModule", "DeleteManyResult", diff --git a/src/modules/auth.types.ts b/src/modules/auth.types.ts index d2b57b2..eff2621 100644 --- a/src/modules/auth.types.ts +++ b/src/modules/auth.types.ts @@ -195,11 +195,11 @@ export interface AuthModule { * Initiates an OAuth login flow with one of the built-in providers. Requires a browser environment and can't be used in the backend. * * Supported providers: - * - `'google'` - {@link https://developers.google.com/identity/protocols/oauth2 | Google OAuth}. Enabled by default. - * - `'microsoft'` - {@link https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow | Microsoft OAuth}. Enable Microsoft in your app's authentication settings before specifying this provider. - * - `'facebook'` - {@link https://developers.facebook.com/docs/facebook-login | Facebook Login}. Enable Facebook in your app's authentication settings before using. - * - `'apple'` - {@link https://developer.apple.com/sign-in-with-apple/ | Sign in with Apple}. Enable Apple in your app's authentication settings before using this provider. - * - `'sso'` - Enterprise SSO. Enable SSO in your app's authentication settings before using this provider. + * - `'google'`: {@link https://developers.google.com/identity/protocols/oauth2 | Google OAuth}. Enabled by default. + * - `'microsoft'`: {@link https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow | Microsoft OAuth}. Enable Microsoft in your app's authentication settings before specifying this provider. + * - `'facebook'`: {@link https://developers.facebook.com/docs/facebook-login | Facebook Login}. Enable Facebook in your app's authentication settings before using. + * - `'apple'`: {@link https://developer.apple.com/sign-in-with-apple/ | Sign in with Apple}. Enable Apple in your app's authentication settings before using this provider. + * - `'sso'`: Enterprise SSO. {@link https://docs.base44.com/Setting-up-your-app/Setting-up-SSO | Set up an SSO provider} in your app's authentication settings before using this provider. * * @param provider - The authentication provider to use: `'google'`, `'microsoft'`, `'facebook'`, `'apple'`, or `'sso'`. * @param fromUrl - URL to redirect to after successful authentication. Defaults to `'/'`. @@ -227,6 +227,7 @@ export interface AuthModule { * // SSO * base44.auth.loginWithProvider('sso', '/dashboard'); * ``` + * */ loginWithProvider(provider: string, fromUrl?: string): void; diff --git a/src/modules/connectors.types.ts b/src/modules/connectors.types.ts index 9360f3d..d831671 100644 --- a/src/modules/connectors.types.ts +++ b/src/modules/connectors.types.ts @@ -14,9 +14,10 @@ export interface ConnectorIntegrationTypeRegistry {} * const token = connection.accessToken; * ``` */ -export type ConnectorIntegrationType = keyof ConnectorIntegrationTypeRegistry extends never - ? string - : keyof ConnectorIntegrationTypeRegistry; +export type ConnectorIntegrationType = + keyof ConnectorIntegrationTypeRegistry extends never + ? string + : keyof ConnectorIntegrationTypeRegistry; /** * Response from the connectors access token endpoint. @@ -28,7 +29,7 @@ export interface ConnectorAccessTokenResponse { } /** - * Camel-cased connection details returned by {@linkcode ConnectorsModule.getConnection | getConnection()}. + * Connection details. */ export interface ConnectorConnectionResponse { /** The OAuth access token for the external service. */ @@ -40,13 +41,45 @@ export interface ConnectorConnectionResponse { /** * Connectors module for managing OAuth tokens for external services. * - * This module allows you to retrieve OAuth access tokens for external services that the app has connected to. Connectors are app-scoped. When an app builder connects an integration like Google Calendar or Slack, all users of the app share that same connection. + * This module allows you to retrieve OAuth access tokens for external services that the app has connected to. Connectors are app-scoped. When an app builder connects an integration like Google Calendar, Slack, or GitHub, all users of the app share that same connection. * * Unlike the integrations module that provides pre-built functions, connectors give you * raw OAuth tokens so you can call external service APIs directly with full control over * the API calls you make. This is useful when you need custom API interactions that aren't * covered by Base44's pre-built integrations. * + * ## Available connectors + * + * All connectors work through [`getConnection()`](#getconnection). Pass the integration type string and use the returned OAuth token to call the external service's API directly. + * + * | Service | Type identifier | + * |---|---| + * | Box | `box` | + * | ClickUp | `clickup` | + * | Discord | `discord` | + * | GitHub | `github` | + * | Gmail | `gmail` | + * | Google Analytics | `google_analytics` | + * | Google BigQuery | `googlebigquery` | + * | Google Calendar | `googlecalendar` | + * | Google Docs | `googledocs` | + * | Google Drive | `googledrive` | + * | Google Sheets | `googlesheets` | + * | Google Slides | `googleslides` | + * | HubSpot | `hubspot` | + * | LinkedIn | `linkedin` | + * | Notion | `notion` | + * | Salesforce | `salesforce` | + * | Slack User | `slack` | + * | Slack Bot | `slackbot` | + * | TikTok | `tiktok` | + * | Wrike | `wrike` | + * + * See the integration guides for more details: + * + * - **Scopes and permissions**: {@link https://docs.base44.com/Integrations/gmail-connector#gmail-scopes-and-permissions | Gmail}, {@link https://docs.base44.com/Integrations/linkedin-connector#linkedin-scopes-and-permissions | LinkedIn}, {@link https://docs.base44.com/Integrations/slack-connector#slack-scopes-and-permissions | Slack} + * - **Slack connector types**: {@link https://docs.base44.com/Integrations/slack-connector#about-the-slack-connectors | About the Slack connectors} explains the difference between `slack` and `slackbot` + * * ## Authentication Modes * * This module is only available to use with a client in service role authentication mode, which means it can only be used in backend environments. @@ -57,15 +90,15 @@ export interface ConnectorConnectionResponse { */ export interface ConnectorsModule { /** - * Retrieves an OAuth access token for a specific external integration type. + * Retrieves an OAuth access token for a specific [external integration type](#available-connectors). * - * @deprecated Use {@link getConnection} and use the returned `accessToken` (and `connectionConfig` when needed) instead. + * @deprecated Use {@link getConnection} instead. * * Returns the OAuth token string for an external service that an app builder * has connected to. This token represents the connected app builder's account * and can be used to make authenticated API calls to that external service on behalf of the app. * - * @param integrationType - The type of integration, such as `'googlecalendar'`, `'slack'`, or `'github'`. + * @param integrationType - The type of integration, such as `'googlecalendar'`, `'slack'`, `'slackbot'`, `'github'`, or `'discord'`. See [Available connectors](#available-connectors) for the full list. * @returns Promise resolving to the access token string. * * @example @@ -87,8 +120,8 @@ export interface ConnectorsModule { * * @example * ```typescript - * // Slack connection - * // Get Slack OAuth token and list channels + * // Slack User connection + * // Get Slack user token and list channels * const slackToken = await base44.asServiceRole.connectors.getAccessToken('slack'); * * // List all public and private channels @@ -100,44 +133,91 @@ export interface ConnectorsModule { * * const data = await slackResponse.json(); * ``` + * + * @example + * ```typescript + * // Slack Bot connection + * // Get Slack bot token and post a message with a custom bot identity + * const botToken = await base44.asServiceRole.connectors.getAccessToken('slackbot'); + * + * const response = await fetch('https://slack.com/api/chat.postMessage', { + * method: 'POST', + * headers: { + * 'Authorization': `Bearer ${botToken}`, + * 'Content-Type': 'application/json' + * }, + * body: JSON.stringify({ + * channel: '#alerts', + * text: 'Deployment to production completed successfully.', + * username: 'Deploy Bot', + * icon_emoji: ':rocket:' + * }) + * }); + * + * const result = await response.json(); + * ``` */ getAccessToken(integrationType: ConnectorIntegrationType): Promise; /** - * Retrieves the OAuth access token and connection configuration for a specific external integration type. + * Retrieves the OAuth access token and connection configuration for a specific [external integration type](#available-connectors). * - * Returns both the OAuth token and any additional connection configuration - * that the connector provides. This is useful when the external service requires - * extra parameters beyond the access token (e.g., a shop domain, account ID, or API base URL). + * Some connectors require connection-specific parameters to build API calls. + * In such cases, the returned `connectionConfig` is an object with the additional parameters. If there are no extra parameters needed for the connection, the `connectionConfig` is `null`. * - * @param integrationType - The type of integration, such as `'googlecalendar'`, `'slack'`, or `'github'`. + * For example, a service might need a subdomain to construct the API URL in + * the form of `{subdomain}.example.com`. In such a case the subdomain will be available as a property of the `connectionConfig` object. + * + * @param integrationType - The type of integration, such as `'googlecalendar'`, `'slack'`, `'slackbot'`, `'github'`, or `'discord'`. See [Available connectors](#available-connectors) for the full list. * @returns Promise resolving to a {@link ConnectorConnectionResponse} with `accessToken` and `connectionConfig`. * * @example * ```typescript - * // Basic usage - * const connection = await base44.asServiceRole.connectors.getConnection('googlecalendar'); - * console.log(connection.accessToken); - * console.log(connection.connectionConfig); + * // Google Calendar connection + * // Get Google Calendar OAuth token and fetch upcoming events + * const { accessToken } = await base44.asServiceRole.connectors.getConnection('googlecalendar'); + * + * const timeMin = new Date().toISOString(); + * const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events?maxResults=10&orderBy=startTime&singleEvents=true&timeMin=${timeMin}`; + * + * const calendarResponse = await fetch(url, { + * headers: { Authorization: `Bearer ${accessToken}` } + * }); + * + * const events = await calendarResponse.json(); * ``` * * @example * ```typescript - * // Shopify: connectionConfig has subdomain (e.g. "my-store" for my-store.myshopify.com) - * const connection = await base44.asServiceRole.connectors.getConnection('shopify'); - * const { accessToken, connectionConfig } = connection; - * const shop = connectionConfig?.subdomain - * ? `https://${connectionConfig.subdomain}.myshopify.com` - * : null; - * - * if (shop) { - * const response = await fetch( - * `${shop}/admin/api/2024-01/products.json?limit=10`, - * { headers: { 'X-Shopify-Access-Token': accessToken } } - * ); - * const { products } = await response.json(); - * } + * // Slack connection + * // Get Slack OAuth token and list channels + * const { accessToken } = await base44.asServiceRole.connectors.getConnection('slack'); + * + * const url = 'https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=100'; + * + * const slackResponse = await fetch(url, { + * headers: { Authorization: `Bearer ${accessToken}` } + * }); + * + * const data = await slackResponse.json(); + * ``` + * + * @example + * ```typescript + * // Using connectionConfig + * // Some connectors return a subdomain or other params needed to build the API URL + * const { accessToken, connectionConfig } = await base44.asServiceRole.connectors.getConnection('myservice'); + * + * const subdomain = connectionConfig?.subdomain; + * const response = await fetch( + * `https://${subdomain}.example.com/api/v1/resources`, + * { headers: { Authorization: `Bearer ${accessToken}` } } + * ); + * + * const data = await response.json(); * ``` */ - getConnection(integrationType: ConnectorIntegrationType): Promise; + getConnection( + integrationType: ConnectorIntegrationType, + ): Promise; }