From 8f93dd83162cb36a302e113acb162866fa68dab9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:37:43 +0000 Subject: [PATCH 1/4] Initial plan From 1b8ae5e78a24922786d75d47c65b9828e1ab2ed9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:43:20 +0000 Subject: [PATCH 2/4] Add message reactions documentation with examples for all languages Co-authored-by: rido-min <14916339+rido-min@users.noreply.github.com> --- .../include/essentials/api/csharp.incl.md | 2 +- .../include/essentials/api/python.incl.md | 2 +- .../include/essentials/api/typescript.incl.md | 2 +- .../message-reactions/csharp.incl.md | 80 +++++++++++++++++++ .../message-reactions/python.incl.md | 79 ++++++++++++++++++ .../message-reactions/typescript.incl.md | 77 ++++++++++++++++++ .../in-depth-guides/message-reactions.mdx | 52 ++++++++++++ 7 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 teams.md/src/components/include/in-depth-guides/message-reactions/csharp.incl.md create mode 100644 teams.md/src/components/include/in-depth-guides/message-reactions/python.incl.md create mode 100644 teams.md/src/components/include/in-depth-guides/message-reactions/typescript.incl.md create mode 100644 teams.md/src/pages/templates/in-depth-guides/message-reactions.mdx diff --git a/teams.md/src/components/include/essentials/api/csharp.incl.md b/teams.md/src/components/include/essentials/api/csharp.incl.md index 8cc589390..26a70d511 100644 --- a/teams.md/src/components/include/essentials/api/csharp.incl.md +++ b/teams.md/src/components/include/essentials/api/csharp.incl.md @@ -6,7 +6,7 @@ | Area | Description | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user) | +| `Conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user). Also includes `Reactions` for adding/removing emoji reactions to messages | | `Meetings` | Gives your application access to meeting details and participant information via `GetByIdAsync` and `GetParticipantAsync` | | `Teams` | Gives your application access to team or channel details | diff --git a/teams.md/src/components/include/essentials/api/python.incl.md b/teams.md/src/components/include/essentials/api/python.incl.md index 4fc536918..cc0c25457 100644 --- a/teams.md/src/components/include/essentials/api/python.incl.md +++ b/teams.md/src/components/include/essentials/api/python.incl.md @@ -6,7 +6,7 @@ | Area | Description | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user) | +| `conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user). Also includes `reactions` for adding/removing emoji reactions to messages | | `meetings` | Gives your application access to meeting details and participant information via `get_by_id` and `get_participant` | | `teams` | Gives your application access to team or channel details | diff --git a/teams.md/src/components/include/essentials/api/typescript.incl.md b/teams.md/src/components/include/essentials/api/typescript.incl.md index a440f973a..c38dd70d1 100644 --- a/teams.md/src/components/include/essentials/api/typescript.incl.md +++ b/teams.md/src/components/include/essentials/api/typescript.incl.md @@ -6,7 +6,7 @@ | Area | Description | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user) | +| `conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user). Also includes `reactions` for adding/removing emoji reactions to messages | | `meetings` | Gives your application access to meeting details and participant information via `getById` and `getParticipant` | | `teams` | Gives your application access to team or channel details | diff --git a/teams.md/src/components/include/in-depth-guides/message-reactions/csharp.incl.md b/teams.md/src/components/include/in-depth-guides/message-reactions/csharp.incl.md new file mode 100644 index 000000000..647df0a84 --- /dev/null +++ b/teams.md/src/components/include/in-depth-guides/message-reactions/csharp.incl.md @@ -0,0 +1,80 @@ + + +```csharp +app.OnMessage(async context => +{ + await context.Send("Hello! I'll react to this message."); + + // Add a reaction to the incoming message + await context.Api.Conversations.Reactions.AddAsync( + context.Activity.Conversation.Id, + context.Activity.Id, + ReactionType.Like + ); +}); +``` + + + +```csharp +app.OnMessage(async context => +{ + // First, add a reaction + await context.Api.Conversations.Reactions.AddAsync( + context.Activity.Conversation.Id, + context.Activity.Id, + ReactionType.Heart + ); + + // Wait a bit, then remove it + await Task.Delay(2000); + await context.Api.Conversations.Reactions.DeleteAsync( + context.Activity.Conversation.Id, + context.Activity.Id, + ReactionType.Heart + ); +}); +``` + + + +The following reaction types are available: + +- `ReactionType.Like` — šŸ‘ +- `ReactionType.Heart` — ā¤ļø +- `ReactionType.Checkmark` — āœ… +- `ReactionType.Hourglass` — ā³ +- `ReactionType.Pushpin` — šŸ“Œ +- `ReactionType.Exclamation` — ā— +- `ReactionType.Laugh` — šŸ˜† +- `ReactionType.Surprise` — 😮 +- `ReactionType.Sad` — šŸ™ +- `ReactionType.Angry` — 😠 + +You can also use custom reaction types by creating a new `ReactionType` instance: + +```csharp +// Use a custom emoji reaction +var customReaction = new ReactionType("1f44b_wavinghand-tone4"); + +await context.Api.Conversations.Reactions.AddAsync( + context.Activity.Conversation.Id, + context.Activity.Id, + customReaction +); +``` + + + +For advanced scenarios where you need to use a custom service URL or access the HTTP client directly, you can use the `ApiClient.Client` property: + +```csharp +// Access the underlying HTTP client for custom requests +var api = new ApiClient(context.Activity.ServiceUrl, context.Api.Client); + +await api.Conversations.Reactions.AddAsync( + context.Activity.Conversation.Id, + context.Activity.Id, + ReactionType.Like +); +``` diff --git a/teams.md/src/components/include/in-depth-guides/message-reactions/python.incl.md b/teams.md/src/components/include/in-depth-guides/message-reactions/python.incl.md new file mode 100644 index 000000000..8bd33d0a8 --- /dev/null +++ b/teams.md/src/components/include/in-depth-guides/message-reactions/python.incl.md @@ -0,0 +1,79 @@ + + +```python +@app.on_message +async def handle_message(ctx: ActivityContext[MessageActivity]): + await ctx.send("Hello! I'll react to this message.") + + # Add a reaction to the incoming message + await ctx.api.conversations.reactions.add( + ctx.activity.conversation.id, + ctx.activity.id, + 'like' + ) +``` + + + +```python +import asyncio + +@app.on_message +async def handle_message(ctx: ActivityContext[MessageActivity]): + # First, add a reaction + await ctx.api.conversations.reactions.add( + ctx.activity.conversation.id, + ctx.activity.id, + 'heart' + ) + + # Wait a bit, then remove it + await asyncio.sleep(2) + await ctx.api.conversations.reactions.delete( + ctx.activity.conversation.id, + ctx.activity.id, + 'heart' + ) +``` + + + +The following reaction types are available: + +- `'like'` — šŸ‘ +- `'heart'` — ā¤ļø +- `'checkmark'` — āœ… +- `'hourglass'` — ā³ +- `'pushpin'` — šŸ“Œ +- `'exclamation'` — ā— +- `'laugh'` — šŸ˜† +- `'surprise'` — 😮 +- `'sad'` — šŸ™ +- `'angry'` — 😠 + +You can also use custom emoji reactions by providing the emoji code: + +```python +# Use a custom emoji reaction +await ctx.api.conversations.reactions.add( + ctx.activity.conversation.id, + ctx.activity.id, + '1f44b_wavinghand-tone4' +) +``` + + + +For advanced scenarios, you can access the API client from the context: + +```python +# The API client is available through the context +api = ctx.api + +# Use reactions API +await api.conversations.reactions.add( + conversation_id, + activity_id, + 'like' +) +``` diff --git a/teams.md/src/components/include/in-depth-guides/message-reactions/typescript.incl.md b/teams.md/src/components/include/in-depth-guides/message-reactions/typescript.incl.md new file mode 100644 index 000000000..f145a29d0 --- /dev/null +++ b/teams.md/src/components/include/in-depth-guides/message-reactions/typescript.incl.md @@ -0,0 +1,77 @@ + + +```typescript +app.on('message', async ({ activity, api, send }) => { + await send("Hello! I'll react to this message."); + + // Add a reaction to the incoming message + await api.conversations.reactions.add( + activity.conversation.id, + activity.id, + 'like' + ); +}); +``` + + + +```typescript +app.on('message', async ({ activity, api }) => { + // First, add a reaction + await api.conversations.reactions.add( + activity.conversation.id, + activity.id, + 'heart' + ); + + // Wait a bit, then remove it + await new Promise(resolve => setTimeout(resolve, 2000)); + await api.conversations.reactions.delete( + activity.conversation.id, + activity.id, + 'heart' + ); +}); +``` + + + +The following reaction types are available: + +- `'like'` — šŸ‘ +- `'heart'` — ā¤ļø +- `'checkmark'` — āœ… +- `'hourglass'` — ā³ +- `'pushpin'` — šŸ“Œ +- `'exclamation'` — ā— +- `'laugh'` — šŸ˜† +- `'surprise'` — 😮 +- `'sad'` — šŸ™ +- `'angry'` — 😠 + +You can also use custom emoji reactions by providing the emoji code: + +```typescript +// Use a custom emoji reaction +await api.conversations.reactions.add( + activity.conversation.id, + activity.id, + '1f44b_wavinghand-tone4' +); +``` + + + +For advanced scenarios, you can access the underlying HTTP client or create a custom API client instance: + +```typescript +// The API client is available through the context +const { api } = context; + +// Use reactions API +await api.conversations.reactions.add( + conversationId, + activityId, + 'like' +); +``` diff --git a/teams.md/src/pages/templates/in-depth-guides/message-reactions.mdx b/teams.md/src/pages/templates/in-depth-guides/message-reactions.mdx new file mode 100644 index 000000000..4dfe32b11 --- /dev/null +++ b/teams.md/src/pages/templates/in-depth-guides/message-reactions.mdx @@ -0,0 +1,52 @@ +--- +sidebar_position: 8 +title: Message Reactions +sidebar_label: Message Reactions +summary: Guide to adding and removing message reactions in Teams applications, including available reaction types and best practices for implementing reaction functionality. +--- + +# Message Reactions + +Message reactions allow your bot to add emoji reactions to messages in Teams conversations. This feature enables your bot to provide quick, non-verbal feedback to user messages, enhancing the interactive experience. + +## Adding a Reaction + +To add a reaction to a message, use the `Reactions` API available through the conversations client: + + + +## Removing a Reaction + +You can also remove reactions that your bot has added: + + + +## Available Reaction Types + + + +:::info Custom Reactions +In addition to the standard reaction types listed above, you can use any valid Teams emoji reaction code. Teams supports a wide variety of emoji reactions beyond the standard set. +::: + +## Advanced Usage + + + +## Best Practices + +When working with message reactions, consider the following: + +- **Use reactions sparingly**: Reactions work best when used to provide meaningful feedback rather than reacting to every message. +- **Consider the context**: Different reaction types convey different meanings. Choose reactions that align with your bot's purpose and the conversation context. +- **Handle errors gracefully**: Reaction operations may fail (e.g., if the message no longer exists). Always handle potential errors appropriately. +- **Be aware of rate limits**: Like other bot operations, reaction operations are subject to Teams API rate limits. + +## Differences from Feedback + +Message reactions are different from the [feedback](./feedback) feature: + +- **Reactions** are quick emoji responses that your bot adds to messages programmatically +- **Feedback** are interactive UI components (like/dislike buttons) that users can click to provide structured feedback on bot responses + +Use reactions when your bot wants to acknowledge or respond to messages with emoji. Use feedback when you want to collect user opinions about your bot's responses. From 35094f0bd07c16443d43e415efa9632450bfa4df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:46:34 +0000 Subject: [PATCH 3/4] Apply prettier formatting to documentation files Co-authored-by: rido-min <14916339+rido-min@users.noreply.github.com> --- teams.md/LANGUAGE-INCLUDE.md | 5 +- teams.md/docs/main/privacy.md | 2 +- .../teams/app-authentication/client-secret.md | 1 + .../federated-identity-credentials.md | 1 + .../app-authentication/troubleshooting.md | 1 + .../user-managed-identity.md | 1 + teams.md/docs/main/teams/core-concepts.md | 2 +- .../docs/main/teams/enabling-in-copilot.md | 6 +- .../teams/user-authentication/_category_.json | 4 +- .../teams/user-authentication/sso-setup.md | 6 +- teams.md/docusaurus.config.ts | 360 +++---- teams.md/scripts/generate-language-docs.ts | 33 +- teams.md/scripts/generate-llms-txt.ts | 937 +++++++++--------- teams.md/scripts/lib/content-processor.ts | 643 ++++++------ teams.md/scripts/lib/file-collector.ts | 444 +++++---- teams.md/scripts/lib/frontmatter-parser.ts | 293 +++--- teams.md/sidebars.ts | 2 +- teams.md/src/components/FileCodeBlock.tsx | 54 +- .../include/essentials/api/csharp.incl.md | 10 +- .../include/essentials/api/python.incl.md | 8 +- .../include/essentials/api/typescript.incl.md | 8 +- .../app-authentication/csharp.incl.md | 3 + .../include/essentials/graph/csharp.incl.md | 2 - .../essentials/on-activity/csharp.incl.md | 22 +- .../sending-messages/csharp.incl.md | 12 +- .../proactive-messaging/csharp.incl.md | 2 +- .../proactive-messaging/python.incl.md | 2 +- .../proactive-messaging/typescript.incl.md | 10 +- .../sending-messages/typescript.incl.md | 3 +- .../code-basics/csharp.incl.md | 1 - .../running-in-teams/csharp.incl.md | 2 +- .../running-in-teams/python.incl.md | 2 +- .../running-in-teams/typescript.incl.md | 2 +- .../executing-actions/python.incl.md | 2 +- .../ai/a2a/a2a-client/python.incl.md | 1 - .../ai/a2a/a2a-server/typescript.incl.md | 1 - .../ai/function-calling/csharp.incl.md | 1 + .../ai/function-calling/python.incl.md | 1 + .../ai/setup-and-prereqs/typescript.incl.md | 9 +- .../meeting-events/typescript.incl.md | 12 +- .../message-reactions/csharp.incl.md | 4 +- .../message-reactions/python.incl.md | 4 +- .../message-reactions/typescript.incl.md | 30 +- .../user-authentication/csharp.incl.md | 3 +- .../user-authentication/python.incl.md | 14 +- .../user-authentication/typescript.incl.md | 16 +- .../botbuilder/integration/csharp.incl.md | 2 +- .../botbuilder/integration/python.incl.md | 2 +- .../proactive-activities/csharp.incl.md | 93 +- .../proactive-activities/python.incl.md | 100 +- .../proactive-activities/typescript.incl.md | 116 ++- .../sending-activities/csharp.incl.md | 413 ++++---- .../sending-activities/python.incl.md | 300 +++--- .../sending-activities/typescript.incl.md | 90 +- .../botbuilder/the-api-client/csharp.incl.md | 78 +- .../botbuilder/the-api-client/python.incl.md | 29 +- .../the-api-client/typescript.incl.md | 92 +- .../user-authentication/typescript.incl.md | 1 + .../migrations/slack-bolt/typescript.incl.md | 768 +++++++------- teams.md/src/pages/index.tsx | 2 +- teams.md/src/scripts/scaffold.js | 27 +- teams.md/src/utils/normalizePath.ts | 4 +- teams.md/src/utils/pageAvailability.ts | 5 +- teams.md/static/scripts/clarity.js | 17 +- 64 files changed, 2655 insertions(+), 2466 deletions(-) diff --git a/teams.md/LANGUAGE-INCLUDE.md b/teams.md/LANGUAGE-INCLUDE.md index 740e080c5..a542a30c1 100644 --- a/teams.md/LANGUAGE-INCLUDE.md +++ b/teams.md/LANGUAGE-INCLUDE.md @@ -63,6 +63,7 @@ Shared content for all languages. The package name is . + The context type is . ``` @@ -103,15 +104,17 @@ N/A For simple, short language-specific text (like API names, method names, or simple phrases), you can use inline content directly in templates without creating separate include files: ```mdx - + ``` **When to use inline content:** + - Short text snippets (API names, method names, parameter names) - Simple differences between languages - Content that's easier to read inline than in separate files **When to use include files:** + - Code examples - Complex or multi-line content - Content that benefits from syntax highlighting diff --git a/teams.md/docs/main/privacy.md b/teams.md/docs/main/privacy.md index 3ddaf4b1f..14acb92bf 100644 --- a/teams.md/docs/main/privacy.md +++ b/teams.md/docs/main/privacy.md @@ -6,4 +6,4 @@ llms: ignore # Privacy Policy -We partner with Microsoft Clarity to capture how you use and interact with our website through behavioral metrics, heatmaps, and session replay to improve and market our products/services. Website usage data is captured using first and third-party cookies and other tracking technologies to determine the popularity of products/services and online activity. Additionally, we use this information for site optimization and fraud/security purposes. For more information about how Microsoft collects and uses your data, visit the [Microsoft Privacy Statement](https://www.microsoft.com/en-us/privacy/privacystatement). \ No newline at end of file +We partner with Microsoft Clarity to capture how you use and interact with our website through behavioral metrics, heatmaps, and session replay to improve and market our products/services. Website usage data is captured using first and third-party cookies and other tracking technologies to determine the popularity of products/services and online activity. Additionally, we use this information for site optimization and fraud/security purposes. For more information about how Microsoft collects and uses your data, visit the [Microsoft Privacy Statement](https://www.microsoft.com/en-us/privacy/privacystatement). diff --git a/teams.md/docs/main/teams/app-authentication/client-secret.md b/teams.md/docs/main/teams/app-authentication/client-secret.md index 891061929..45e0bcf83 100644 --- a/teams.md/docs/main/teams/app-authentication/client-secret.md +++ b/teams.md/docs/main/teams/app-authentication/client-secret.md @@ -14,6 +14,7 @@ Client Secret authentication is the simplest method, using a password-like secre ## Prerequisites Before you begin, ensure you have: + - An Azure subscription - Permissions to create App Registrations and Azure Bot Services diff --git a/teams.md/docs/main/teams/app-authentication/federated-identity-credentials.md b/teams.md/docs/main/teams/app-authentication/federated-identity-credentials.md index d4d95116d..fc24b1626 100644 --- a/teams.md/docs/main/teams/app-authentication/federated-identity-credentials.md +++ b/teams.md/docs/main/teams/app-authentication/federated-identity-credentials.md @@ -14,6 +14,7 @@ Federated Identity Credentials (FIC) allows you to assign managed identities dir ## Prerequisites Before you begin, ensure you have: + - An Azure subscription - Permissions to create App Registrations, Azure Bot Services, and manage identities - A compute resource where your bot will be hosted (App Service, Container App, VM, etc.) diff --git a/teams.md/docs/main/teams/app-authentication/troubleshooting.md b/teams.md/docs/main/teams/app-authentication/troubleshooting.md index c75a08d48..29dec2dcc 100644 --- a/teams.md/docs/main/teams/app-authentication/troubleshooting.md +++ b/teams.md/docs/main/teams/app-authentication/troubleshooting.md @@ -76,6 +76,7 @@ This error occurs when the application has a single-tenant Azure Bot Service (`m 3. **Search for your application** Use the **BOT_ID** from your environment file: + - Local development → `env/.env.local` - Azure deployment → `env/.env.dev` diff --git a/teams.md/docs/main/teams/app-authentication/user-managed-identity.md b/teams.md/docs/main/teams/app-authentication/user-managed-identity.md index fffa4072d..7b514f3d0 100644 --- a/teams.md/docs/main/teams/app-authentication/user-managed-identity.md +++ b/teams.md/docs/main/teams/app-authentication/user-managed-identity.md @@ -14,6 +14,7 @@ User Managed Identity authentication eliminates the need for secrets or password ## Prerequisites Before you begin, ensure you have: + - An Azure subscription - Permissions to create App Registrations, Azure Bot Services, and manage identities - A compute resource where your bot will be hosted (App Service, Container App, VM, etc.) diff --git a/teams.md/docs/main/teams/core-concepts.md b/teams.md/docs/main/teams/core-concepts.md index eb5d41c93..d1bdd7b12 100644 --- a/teams.md/docs/main/teams/core-concepts.md +++ b/teams.md/docs/main/teams/core-concepts.md @@ -115,4 +115,4 @@ Sideloading needs to be enabled in your tenant. If this is not the case, then yo ## Provisioning and Deployment -To test your app in Teams, you will at minimum need to have a provisioned Azure bot. You are likely to have other provisionied resources such as storage. Please see the Microsoft Learn [Provision cloud resources](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/provision) documentation for provisioning and deployment using Visual Studio Code and to a container service. \ No newline at end of file +To test your app in Teams, you will at minimum need to have a provisioned Azure bot. You are likely to have other provisionied resources such as storage. Please see the Microsoft Learn [Provision cloud resources](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/provision) documentation for provisioning and deployment using Visual Studio Code and to a container service. diff --git a/teams.md/docs/main/teams/enabling-in-copilot.md b/teams.md/docs/main/teams/enabling-in-copilot.md index 44c7b6bd7..b7a624133 100644 --- a/teams.md/docs/main/teams/enabling-in-copilot.md +++ b/teams.md/docs/main/teams/enabling-in-copilot.md @@ -68,11 +68,7 @@ Here's how the `copilotAgents` section fits into the overall manifest structure: "bots": [ { "botId": "${{BOT_ID}}", - "scopes": [ - "personal", - "team", - "groupchat" - ], + "scopes": ["personal", "team", "groupchat"], "supportsFiles": false, "isNotificationOnly": false } diff --git a/teams.md/docs/main/teams/user-authentication/_category_.json b/teams.md/docs/main/teams/user-authentication/_category_.json index 2374d63e6..bdc8d624e 100644 --- a/teams.md/docs/main/teams/user-authentication/_category_.json +++ b/teams.md/docs/main/teams/user-authentication/_category_.json @@ -1,5 +1,5 @@ { "label": "User Authentication Configuration", - "collapsed": true, - "position": 4, + "collapsed": true, + "position": 4 } diff --git a/teams.md/docs/main/teams/user-authentication/sso-setup.md b/teams.md/docs/main/teams/user-authentication/sso-setup.md index 9e40f804b..fa2aafc5c 100644 --- a/teams.md/docs/main/teams/user-authentication/sso-setup.md +++ b/teams.md/docs/main/teams/user-authentication/sso-setup.md @@ -20,7 +20,7 @@ You need an Entra ID App Registration to configure the OAuth Connection in Azure ![Entra client secret](/screenshots/entra-client-secret.png) -4. Configure the API. From `Expose an API`, Click `Add` to Application ID URI and accept the default value that will look like `api://`. Add the scope `access_as_user` and select who can _consent_. +4. Configure the API. From `Expose an API`, Click `Add` to Application ID URI and accept the default value that will look like `api://`. Add the scope `access_as_user` and select who can _consent_. ![Entra oauth scopes](/screenshots/entra-oauth-scopes.png) @@ -64,10 +64,9 @@ az bot authsetting create \ --parameters "clientId=$appId" "clientSecret=$clientSecret" "tenantId=$tenantId" "tokenExchangeUrl=api://$appId" ``` - ## Configure the App Manifest -The Teams application manifest needs to be updated to reflect the settings configure above, with the `Application Id` and `Application ID URI`, if not using `devtunnels`, replace the valid domain with the domain hosting your application. +The Teams application manifest needs to be updated to reflect the settings configure above, with the `Application Id` and `Application ID URI`, if not using `devtunnels`, replace the valid domain with the domain hosting your application. ```json "validDomains": [ @@ -79,4 +78,3 @@ The Teams application manifest needs to be updated to reflect the settings confi "resource": "api://" } ``` - diff --git a/teams.md/docusaurus.config.ts b/teams.md/docusaurus.config.ts index 47fa542dd..9901b29bd 100644 --- a/teams.md/docusaurus.config.ts +++ b/teams.md/docusaurus.config.ts @@ -6,204 +6,204 @@ import { themes as prismThemes } from 'prism-react-renderer'; // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) const baseUrl = '/teams-sdk/'; const config: Config = { - title: 'Teams SDK', - favicon: 'img/msft-logo-48x48.png', + title: 'Teams SDK', + favicon: 'img/msft-logo-48x48.png', - // Set the production url of your site here - url: 'https://microsoft.github.io/', - // Set the // pathname under which your site is served - // For GitHub pages deployment, it is often '//' - baseUrl, + // Set the production url of your site here + url: 'https://microsoft.github.io/', + // Set the // pathname under which your site is served + // For GitHub pages deployment, it is often '//' + baseUrl, - // GitHub pages deployment config. - // If you aren't using GitHub pages, you don't need these. - organizationName: 'microsoft', // Usually your GitHub org/user name. - projectName: baseUrl, // Usually your repo name. + // GitHub pages deployment config. + // If you aren't using GitHub pages, you don't need these. + organizationName: 'microsoft', // Usually your GitHub org/user name. + projectName: baseUrl, // Usually your repo name. - onBrokenLinks: 'throw', + onBrokenLinks: 'throw', - // Even if you don't use internationalization, you can use this field to set - // useful metadata like html lang. For example, if your site is Chinese, you - // may want to replace "en" with "zh-Hans". - i18n: { - defaultLocale: 'en', - locales: ['en'], + // Even if you don't use internationalization, you can use this field to set + // useful metadata like html lang. For example, if your site is Chinese, you + // may want to replace "en" with "zh-Hans". + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, + + markdown: { + mermaid: true, + hooks: { + onBrokenMarkdownLinks: 'throw', + }, + }, + headTags: [ + { + tagName: 'link', + attributes: { + rel: 'llms.txt', + href: 'https://microsoft.github.io/teams-sdk/llms_docs/llms.txt', + }, }, + ], + scripts: [path.join(baseUrl, '/scripts/clarity.js')], - markdown: { - mermaid: true, - hooks: { - onBrokenMarkdownLinks: 'throw', + presets: [ + [ + 'classic', + { + blog: false, + docs: { + routeBasePath: '/', + path: 'docs/main', + sidebarPath: './sidebars.ts', + sidebarCollapsed: false, + editUrl: 'https://github.com/microsoft/teams-sdk/tree/main/teams.md/', + // Temporary exclude until generate-LLMs script is fully tested + exclude: ['**/LLMs.md'], }, - }, - headTags: [ - { - tagName: 'link', - attributes: { - rel: 'llms.txt', - href: 'https://microsoft.github.io/teams-sdk/llms_docs/llms.txt' - } - } + pages: { + exclude: ['**/templates/**'], + }, + theme: { + customCss: ['./src/css/custom.css', './src/css/code-blocks.css'], + }, + } satisfies Preset.Options, ], - scripts: [path.join(baseUrl, '/scripts/clarity.js')], + ], - presets: [ - [ - 'classic', - { - blog: false, - docs: { - routeBasePath: '/', - path: 'docs/main', - sidebarPath: './sidebars.ts', - sidebarCollapsed: false, - editUrl: 'https://github.com/microsoft/teams-sdk/tree/main/teams.md/', - // Temporary exclude until generate-LLMs script is fully tested - exclude: ['**/LLMs.md'], - }, - pages: { - exclude: ['**/templates/**'], - }, - theme: { - customCss: ['./src/css/custom.css', './src/css/code-blocks.css'], - }, - } satisfies Preset.Options, - ], + themes: [ + '@docusaurus/theme-mermaid', + [ + require.resolve('@easyops-cn/docusaurus-search-local'), + /** @type {import("@easyops-cn/docusaurus-search-local").PluginOptions} */ + { + hashed: true, + language: ['en'], + docsRouteBasePath: ['/', '/typescript', '/csharp', '/python'], + indexDocs: true, + indexPages: true, + highlightSearchTermsOnTargetPage: true, + }, ], - - themes: [ - '@docusaurus/theme-mermaid', - [ - require.resolve('@easyops-cn/docusaurus-search-local'), - /** @type {import("@easyops-cn/docusaurus-search-local").PluginOptions} */ + ], + themeConfig: { + colorMode: { + respectPrefersColorScheme: true, + }, + navbar: { + title: 'Teams SDK', + hideOnScroll: true, + logo: { + alt: 'Teams SDK', + src: 'img/teams.png', + }, + items: [ + { + href: 'https://github.com/microsoft/teams-sdk/tree/main', + position: 'right', + className: 'header-github-link', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Docs', + items: [ { - hashed: true, - language: ['en'], - docsRouteBasePath: ['/', '/typescript', '/csharp', '/python'], - indexDocs: true, - indexPages: true, - highlightSearchTermsOnTargetPage: true, + label: 'Getting Started', + to: '/', }, - ], - ], - themeConfig: { - colorMode: { - respectPrefersColorScheme: true, + { + label: 'TypeScript', + to: '/typescript/getting-started', + }, + { + label: 'C#', + to: '/csharp/getting-started', + }, + { + label: 'Python', + to: '/python/getting-started', + }, + { + label: 'Privacy policy', + to: '/privacy', + }, + ], }, - navbar: { - title: 'Teams SDK', - hideOnScroll: true, - logo: { - alt: 'Teams SDK', - src: 'img/teams.png', + { + title: 'More', + items: [ + { + label: 'GitHub', + href: 'https://github.com/microsoft/teams-sdk/tree/main', + }, + { + label: 'Contributing', + href: 'https://github.com/microsoft/teams-sdk/blob/main/CONTRIBUTING.md', + }, + { + label: 'Blog', + href: 'https://devblogs.microsoft.com/microsoft365dev/announcing-the-updated-teams-ai-library-and-mcp-support/', + }, + { + label: 'Teams agent accelerator templates', + href: 'https://microsoft.github.io/teams-agent-accelerator-templates/', }, - items: [ - { - href: 'https://github.com/microsoft/teams-sdk/tree/main', - position: 'right', - className: 'header-github-link', - }, - ], + ], }, - footer: { - style: 'dark', - links: [ - { - title: 'Docs', - items: [ - { - label: 'Getting Started', - to: '/', - }, - { - label: 'TypeScript', - to: '/typescript/getting-started', - }, - { - label: 'C#', - to: '/csharp/getting-started', - }, - { - label: 'Python', - to: '/python/getting-started', - }, - { - label: 'Privacy policy', - to: '/privacy', - }, - ], - }, - { - title: 'More', - items: [ - { - label: 'GitHub', - href: 'https://github.com/microsoft/teams-sdk/tree/main', - }, - { - label: 'Contributing', - href: 'https://github.com/microsoft/teams-sdk/blob/main/CONTRIBUTING.md', - }, - { - label: 'Blog', - href: 'https://devblogs.microsoft.com/microsoft365dev/announcing-the-updated-teams-ai-library-and-mcp-support/', - }, - { - label: 'Teams agent accelerator templates', - href: 'https://microsoft.github.io/teams-agent-accelerator-templates/', - }, - ], - }, - ], - copyright: `Copyright Ā© ${new Date().getFullYear()} Microsoft Corporation. All rights reserved.`, + ], + copyright: `Copyright Ā© ${new Date().getFullYear()} Microsoft Corporation. All rights reserved.`, + }, + prism: { + theme: prismThemes.dracula, + darkTheme: prismThemes.vsDark, + magicComments: [ + { + className: 'theme-code-block-highlighted-line', + line: 'highlight-next-line', + block: { + start: 'highlight-start', + end: 'highlight-end', + }, }, - prism: { - theme: prismThemes.dracula, - darkTheme: prismThemes.vsDark, - magicComments: [ - { - className: 'theme-code-block-highlighted-line', - line: 'highlight-next-line', - block: { - start: 'highlight-start', - end: 'highlight-end', - }, - }, - { - className: 'code-block-error-line', - line: 'highlight-error-line', - block: { - start: 'highlight-error-start', - end: 'highlight-error-end', - }, - }, - { - className: 'code-block-success-line', - line: 'highlight-success-line', - block: { - start: 'highlight-success-start', - end: 'highlight-success-end', - }, - }, - ], - additionalLanguages: [ - 'typescript', - 'javascript', - 'csharp', - 'python', - 'bash', - 'markdown', - 'json', - ], + { + className: 'code-block-error-line', + line: 'highlight-error-line', + block: { + start: 'highlight-error-start', + end: 'highlight-error-end', + }, }, - announcementBar: { - id: 'teams-sdk-rename', - content: 'We have been renamed to Teams SDK! šŸŽ‰ 🄳', - isCloseable: true, - backgroundColor: '#515cc6', - textColor: '#fff' + { + className: 'code-block-success-line', + line: 'highlight-success-line', + block: { + start: 'highlight-success-start', + end: 'highlight-success-end', + }, }, - } satisfies Preset.ThemeConfig, + ], + additionalLanguages: [ + 'typescript', + 'javascript', + 'csharp', + 'python', + 'bash', + 'markdown', + 'json', + ], + }, + announcementBar: { + id: 'teams-sdk-rename', + content: 'We have been renamed to Teams SDK! šŸŽ‰ 🄳', + isCloseable: true, + backgroundColor: '#515cc6', + textColor: '#fff', + }, + } satisfies Preset.ThemeConfig, }; export default config; diff --git a/teams.md/scripts/generate-language-docs.ts b/teams.md/scripts/generate-language-docs.ts index 2b4eea75b..0552831f5 100644 --- a/teams.md/scripts/generate-language-docs.ts +++ b/teams.md/scripts/generate-language-docs.ts @@ -39,7 +39,8 @@ const SECTION_REGEX = (sectionName: string) => new RegExp(`\\s*([\\s\\S]*?)(?=|$)`, 'i'); // Regex to find LanguageInclude tags (supports both section and content props) -const LANGUAGE_INCLUDE_REGEX = //g; +const LANGUAGE_INCLUDE_REGEX = + //g; const languagePattern = LANGUAGES.join('|'); const LANGUAGE_INCL_FILENAME_REGEX = new RegExp(`^(${languagePattern})\\.incl\\.md$`); @@ -142,13 +143,13 @@ function processLanguageIncludeTags( try { // Parse the inline content object const contentObj = JSON.parse(inlineContent); - + // Production mode with target language if (isProduction && targetLanguage) { const content = contentObj[targetLanguage]; return content || ''; } - + // Development mode: generate Language components for all languages const languageComponents: string[] = []; for (const lang of LANGUAGES) { @@ -183,7 +184,11 @@ function processLanguageIncludeTags( const fileContent = readFileUtf8Normalized(inclPath); const sectionContent = extractSection(fileContent, sectionName); - if (sectionContent === null || sectionContent === '' || sectionContent === 'EMPTY_SECTION') { + if ( + sectionContent === null || + sectionContent === '' || + sectionContent === 'EMPTY_SECTION' + ) { // Skip missing sections (null), intentional N/A content (empty string), or empty sections return ''; } @@ -464,12 +469,16 @@ function generateDocsForTemplate(templatePath: string): void { const processedContent = processLanguageIncludeTags(templateContent, templatePath, lang); // Extract frontmatter if exists - const { frontmatter, hasFrontmatter, content: contentWithoutFrontmatter } = FrontmatterParser.extract(processedContent); + const { + frontmatter, + hasFrontmatter, + content: contentWithoutFrontmatter, + } = FrontmatterParser.extract(processedContent); let content = contentWithoutFrontmatter; let frontmatterRaw = ''; if (hasFrontmatter && frontmatter && Object.keys(frontmatter).length > 0) { - frontmatterRaw = `---\n${yaml.dump(frontmatter)}---\n` + frontmatterRaw = `---\n${yaml.dump(frontmatter)}---\n`; } const outputDir = path.join(DOCS_BASE, lang, path.dirname(relativePath)); @@ -645,7 +654,9 @@ function warnOrphanedIncludeFiles() { } scanDir(FRAGMENTS_DIR); if (orphanedFiles.length > 0) { - console.warn(`\n[DevMode] Orphaned include files were found. These files are not referenced by any template (possibly due to 'language' frontmatter restrictions):`); + console.warn( + `\n[DevMode] Orphaned include files were found. These files are not referenced by any template (possibly due to 'language' frontmatter restrictions):` + ); orphanedFiles.forEach(({ lang, fullPath, relTemplate }) => { console.warn(` - [${lang}] ${fullPath}\n Template: ${relTemplate}`); }); @@ -701,7 +712,7 @@ function writeContentGapsManifest(): void { for (const [templatePath, sections] of Object.entries(contentGapsManifest)) { markdownContent += `## \`${templatePath}\`\n\n`; for (const [sectionName, languages] of Object.entries(sections)) { - const langNames = languages.map(lang => LANGUAGE_NAMES[lang]).join(', '); + const langNames = languages.map((lang) => LANGUAGE_NAMES[lang]).join(', '); markdownContent += `- **\`${sectionName}\`**: Missing in ${langNames}\n`; } markdownContent += `\n`; @@ -710,7 +721,7 @@ function writeContentGapsManifest(): void { markdownContent += `## Summary\n\n`; const totalSectionGaps = Object.values(contentGapsManifest) - .flatMap(sections => Object.values(sections)) + .flatMap((sections) => Object.values(sections)) .reduce((total, langs: Language[]) => total + langs.length, 0); markdownContent += `- **${totalGaps}** templates with gaps\n`; markdownContent += `- **${totalSectionGaps}** total missing sections\n`; @@ -871,7 +882,9 @@ if (require.main === module) { // In production mode, fail the build if there are content gaps if (isProduction && result.contentGapsFound > 0) { - console.error(`\nāŒ Build failed: Found ${result.contentGapsFound} template(s) with content gaps in production mode.`); + console.error( + `\nāŒ Build failed: Found ${result.contentGapsFound} template(s) with content gaps in production mode.` + ); console.error('Please fill in all missing sections or mark them as N/A before deploying.\n'); process.exit(1); } diff --git a/teams.md/scripts/generate-llms-txt.ts b/teams.md/scripts/generate-llms-txt.ts index c0d3023f9..c641c868c 100644 --- a/teams.md/scripts/generate-llms-txt.ts +++ b/teams.md/scripts/generate-llms-txt.ts @@ -10,25 +10,25 @@ import { LANGUAGES, Language, LANGUAGE_NAMES } from '../src/constants/languages' import readFileUtf8Normalized from '../src/utils/readFileUtf8Normalized'; const LANGUAGE_SPECIFIC_TIPS: Record = { - typescript: [ - "It's a good idea to build the application using `npm run build` and fix compile time errors to help ensure the app works as expected.", - "The SDK uses typescript to help you make the right decisions when using the APIs. You may check type definitions and type checkers to make sure your code is correct." - ], - python: [ - "It's a good idea to run `pyright` to make sure the code is correctly typed and fix any type errors.", - ], - csharp: [ - "It's a good idea to build the application and fix compile time errors to help ensure the app works as expected.", - "It is helpful to inspect NuGet packages folder to get exact types for a given namesapce" - ] + typescript: [ + "It's a good idea to build the application using `npm run build` and fix compile time errors to help ensure the app works as expected.", + 'The SDK uses typescript to help you make the right decisions when using the APIs. You may check type definitions and type checkers to make sure your code is correct.', + ], + python: [ + "It's a good idea to run `pyright` to make sure the code is correctly typed and fix any type errors.", + ], + csharp: [ + "It's a good idea to build the application and fix compile time errors to help ensure the app works as expected.", + 'It is helpful to inspect NuGet packages folder to get exact types for a given namesapce', + ], }; const COMMON_OVERALL_SUMMARY = (language: Language) => { - const langName = LANGUAGE_NAMES[language]; - const tips = LANGUAGE_SPECIFIC_TIPS[language]; - const formattedTips = tips.map(tip => `- ${tip}`).join('\n'); + const langName = LANGUAGE_NAMES[language]; + const tips = LANGUAGE_SPECIFIC_TIPS[language]; + const formattedTips = tips.map((tip) => `- ${tip}`).join('\n'); - return `> Microsoft Teams SDK - A comprehensive framework for building AI-powered Teams applications using ${langName}. Using this SDK, you can easily build and integrate a variety of features in Microsoft Teams by building Agents or Tools. The documentation here helps by giving background information and code samples on how best to do this. + return `> Microsoft Teams SDK - A comprehensive framework for building AI-powered Teams applications using ${langName}. Using this SDK, you can easily build and integrate a variety of features in Microsoft Teams by building Agents or Tools. The documentation here helps by giving background information and code samples on how best to do this. IMPORTANT THINGS TO REMEMBER: - This SDK is NOT based off of BotFramework (which the _previous_ version of the Teams SDK was based on). This SDK is a completely new framework. Use this guide to find snippets to drive your decisions. @@ -39,32 +39,32 @@ YOU MUST FOLLOW THE ABOVE GUIDANCE.`; }; interface DocusaurusConfig { - url: string; - baseUrl: string; + url: string; + baseUrl: string; } interface ProcessedFile { - title: string; - content: string; - frontmatter: { [key: string]: any }; - filePath: string; - sidebarPosition: number; - relativeUrl: string; + title: string; + content: string; + frontmatter: { [key: string]: any }; + filePath: string; + sidebarPosition: number; + relativeUrl: string; } interface FileInfo { - name: string; - title: string; - path: string; - order: number; + name: string; + title: string; + path: string; + order: number; } interface FolderStructure { - title: string; - order: number; - path: string; - files: FileInfo[]; - children: { [key: string]: FolderStructure }; + title: string; + order: number; + path: string; + files: FileInfo[]; + children: { [key: string]: FolderStructure }; } /** @@ -73,25 +73,25 @@ interface FolderStructure { * @returns Config object with url and baseUrl */ function getDocusaurusConfig(baseDir: string): DocusaurusConfig { - try { + try { // Read the docusaurus.config.ts file const configPath = path.join(baseDir, 'docusaurus.config.ts'); const configContent = readFileUtf8Normalized(configPath); - // Extract URL and baseUrl using regex (simple approach) - const urlMatch = configContent.match(/url:\s*['"]([^'"]+)['"]/); - const baseUrlMatch = - configContent.match(/baseUrl\s*=\s*['"]([^'"]+)['"]/) || - configContent.match(/baseUrl:\s*['"]([^'"]+)['"]/); + // Extract URL and baseUrl using regex (simple approach) + const urlMatch = configContent.match(/url:\s*['"]([^'"]+)['"]/); + const baseUrlMatch = + configContent.match(/baseUrl\s*=\s*['"]([^'"]+)['"]/) || + configContent.match(/baseUrl:\s*['"]([^'"]+)['"]/); - const url = urlMatch ? urlMatch[1] : 'https://microsoft.github.io'; - const baseUrl = baseUrlMatch ? baseUrlMatch[1] : '/teams-sdk/'; + const url = urlMatch ? urlMatch[1] : 'https://microsoft.github.io'; + const baseUrl = baseUrlMatch ? baseUrlMatch[1] : '/teams-sdk/'; - return { url, baseUrl }; - } catch (error) { - console.warn('āš ļø Could not read Docusaurus config, using defaults'); - return { url: 'https://microsoft.github.io', baseUrl: '/teams-sdk/' }; - } + return { url, baseUrl }; + } catch (error) { + console.warn('āš ļø Could not read Docusaurus config, using defaults'); + return { url: 'https://microsoft.github.io', baseUrl: '/teams-sdk/' }; + } } /** @@ -99,35 +99,35 @@ function getDocusaurusConfig(baseDir: string): DocusaurusConfig { * Creates both small and full versions for TypeScript and C# docs */ async function generateLlmsTxt(): Promise { - console.log('šŸš€ Starting llms.txt generation...'); - - const baseDir = path.join(__dirname, '..'); - const outputDir = path.join(baseDir, 'static', 'llms_docs'); - - // Get Docusaurus configuration - const config = getDocusaurusConfig(baseDir); - const cleanUrl = config.url.replace(/\/$/, ''); - const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl; - console.log(`šŸ“ Using base URL: ${cleanUrl}${cleanBaseUrl}`); - - // Ensure output directory exists - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); + console.log('šŸš€ Starting llms.txt generation...'); + + const baseDir = path.join(__dirname, '..'); + const outputDir = path.join(baseDir, 'static', 'llms_docs'); + + // Get Docusaurus configuration + const config = getDocusaurusConfig(baseDir); + const cleanUrl = config.url.replace(/\/$/, ''); + const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl; + console.log(`šŸ“ Using base URL: ${cleanUrl}${cleanBaseUrl}`); + + // Ensure output directory exists + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + try { + // Generate llms.txt files for all languages + for (const language of LANGUAGES) { + const langName = LANGUAGE_NAMES[language]; + console.log(`šŸ“ Generating ${langName} llms.txt files...`); + await generateLanguageFiles(language, baseDir, outputDir, config); } - try { - // Generate llms.txt files for all languages - for (const language of LANGUAGES) { - const langName = LANGUAGE_NAMES[language]; - console.log(`šŸ“ Generating ${langName} llms.txt files...`); - await generateLanguageFiles(language, baseDir, outputDir, config); - } - - console.log('āœ… Successfully generated all llms.txt files!'); - } catch (error) { - console.error('āŒ Error generating llms.txt files:', error); - process.exit(1); - } + console.log('āœ… Successfully generated all llms.txt files!'); + } catch (error) { + console.error('āŒ Error generating llms.txt files:', error); + process.exit(1); + } } /** @@ -137,49 +137,54 @@ async function generateLlmsTxt(): Promise { * @param outputDir - Output directory path * @param config - Docusaurus config object */ -async function generateLanguageFiles(language: Language, baseDir: string, outputDir: string, config: DocusaurusConfig): Promise { - // Collect all relevant files - const mainFiles: string[] = []; - const langFiles = collectFiles(path.join(baseDir, 'docs', 'main', language)); - - // Process all files to get metadata and file mapping - const { processedFiles, fileMapping } = await processAllFiles( - [...mainFiles, ...langFiles], - baseDir, - language - ); - - // Generate individual TXT files for each doc - await generateIndividualTxtFiles( - processedFiles, - outputDir, - language, - baseDir, - config, - fileMapping - ); - - // Process content for small version (navigation index) - const smallContent = await generateSmallVersionHierarchical( - language, - baseDir, - config, - fileMapping - ); - - // Process content for full version (all documentation) - const fullContent = await generateFullVersion(language, processedFiles, baseDir); - - // Write main llms.txt files - const smallPath = path.join(outputDir, `llms_${language}.txt`); - const fullPath = path.join(outputDir, `llms_${language}_full.txt`); - - fs.writeFileSync(smallPath, smallContent, 'utf8'); - fs.writeFileSync(fullPath, fullContent, 'utf8'); - - console.log(` āœ“ Generated ${path.basename(smallPath)} (${formatBytes(smallContent.length)})`); - console.log(` āœ“ Generated ${path.basename(fullPath)} (${formatBytes(fullContent.length)})`); - console.log(` āœ“ Generated ${processedFiles.length} individual .txt files`); +async function generateLanguageFiles( + language: Language, + baseDir: string, + outputDir: string, + config: DocusaurusConfig +): Promise { + // Collect all relevant files + const mainFiles: string[] = []; + const langFiles = collectFiles(path.join(baseDir, 'docs', 'main', language)); + + // Process all files to get metadata and file mapping + const { processedFiles, fileMapping } = await processAllFiles( + [...mainFiles, ...langFiles], + baseDir, + language + ); + + // Generate individual TXT files for each doc + await generateIndividualTxtFiles( + processedFiles, + outputDir, + language, + baseDir, + config, + fileMapping + ); + + // Process content for small version (navigation index) + const smallContent = await generateSmallVersionHierarchical( + language, + baseDir, + config, + fileMapping + ); + + // Process content for full version (all documentation) + const fullContent = await generateFullVersion(language, processedFiles, baseDir); + + // Write main llms.txt files + const smallPath = path.join(outputDir, `llms_${language}.txt`); + const fullPath = path.join(outputDir, `llms_${language}_full.txt`); + + fs.writeFileSync(smallPath, smallContent, 'utf8'); + fs.writeFileSync(fullPath, fullContent, 'utf8'); + + console.log(` āœ“ Generated ${path.basename(smallPath)} (${formatBytes(smallContent.length)})`); + console.log(` āœ“ Generated ${path.basename(fullPath)} (${formatBytes(fullContent.length)})`); + console.log(` āœ“ Generated ${processedFiles.length} individual .txt files`); } /** @@ -189,48 +194,52 @@ async function generateLanguageFiles(language: Language, baseDir: string, output * @param language - Language identifier for filtering language-specific files * @returns Object with processedFiles array and fileMapping Map */ -async function processAllFiles(allFiles: string[], baseDir: string, language: Language): Promise<{ processedFiles: ProcessedFile[]; fileMapping: Map }> { - const processedFiles: ProcessedFile[] = []; - - // First pass: build file mapping - const fileMapping = new Map(); - for (const file of allFiles) { - // Generate the same filename logic as used in generateIndividualTxtFiles - const tempProcessed = await processContent(file, baseDir, false, null, null, language); // Quick pass for title with language filtering - if (tempProcessed) { - // Only process files that aren't marked to ignore - let fileName: string; - if (path.basename(file) === 'README.md') { - const parentDir = path.basename(path.dirname(file)); - fileName = generateSafeFileName(parentDir); - } else { - fileName = generateSafeFileName(tempProcessed.title || file); - } - fileMapping.set(file, fileName); - } +async function processAllFiles( + allFiles: string[], + baseDir: string, + language: Language +): Promise<{ processedFiles: ProcessedFile[]; fileMapping: Map }> { + const processedFiles: ProcessedFile[] = []; + + // First pass: build file mapping + const fileMapping = new Map(); + for (const file of allFiles) { + // Generate the same filename logic as used in generateIndividualTxtFiles + const tempProcessed = await processContent(file, baseDir, false, null, null, language); // Quick pass for title with language filtering + if (tempProcessed) { + // Only process files that aren't marked to ignore + let fileName: string; + if (path.basename(file) === 'README.md') { + const parentDir = path.basename(path.dirname(file)); + fileName = generateSafeFileName(parentDir); + } else { + fileName = generateSafeFileName(tempProcessed.title || file); + } + fileMapping.set(file, fileName); } + } - // Second pass: process files with mapping for link resolution - for (const file of allFiles) { - const processed = await processContent(file, baseDir, true, fileMapping, null, language); - if (processed && (processed.title || processed.content)) { - processedFiles.push(processed); - } + // Second pass: process files with mapping for link resolution + for (const file of allFiles) { + const processed = await processContent(file, baseDir, true, fileMapping, null, language); + if (processed && (processed.title || processed.content)) { + processedFiles.push(processed); } + } - // Sort by sidebar position, then by title - const sortedFiles = processedFiles.sort((a, b) => { - const posA = a.sidebarPosition || 999; - const posB = b.sidebarPosition || 999; + // Sort by sidebar position, then by title + const sortedFiles = processedFiles.sort((a, b) => { + const posA = a.sidebarPosition || 999; + const posB = b.sidebarPosition || 999; - if (posA !== posB) { - return posA - posB; - } + if (posA !== posB) { + return posA - posB; + } - return (a.title || '').localeCompare(b.title || ''); - }); + return (a.title || '').localeCompare(b.title || ''); + }); - return { processedFiles: sortedFiles, fileMapping }; + return { processedFiles: sortedFiles, fileMapping }; } /** @@ -243,53 +252,53 @@ async function processAllFiles(allFiles: string[], baseDir: string, language: La * @param fileMapping - File mapping for link resolution */ async function generateIndividualTxtFiles( - processedFiles: ProcessedFile[], - outputDir: string, - language: Language, - baseDir: string, - config: DocusaurusConfig, - fileMapping: Map + processedFiles: ProcessedFile[], + outputDir: string, + language: Language, + baseDir: string, + config: DocusaurusConfig, + fileMapping: Map ): Promise { - const docsDir = path.join(outputDir, `docs_${language}`); - - // Clean and recreate docs directory to remove old files - if (fs.existsSync(docsDir)) { - fs.rmSync(docsDir, { recursive: true }); - } - fs.mkdirSync(docsDir, { recursive: true }); - - for (const file of processedFiles) { - if (!file.content) continue; - - // Re-process the file with full URL generation for individual files - const reprocessed = await processContent( - file.filePath, - baseDir, - true, - fileMapping, - config, - language - ); + const docsDir = path.join(outputDir, `docs_${language}`); + + // Clean and recreate docs directory to remove old files + if (fs.existsSync(docsDir)) { + fs.rmSync(docsDir, { recursive: true }); + } + fs.mkdirSync(docsDir, { recursive: true }); + + for (const file of processedFiles) { + if (!file.content) continue; + + // Re-process the file with full URL generation for individual files + const reprocessed = await processContent( + file.filePath, + baseDir, + true, + fileMapping, + config, + language + ); - if (!reprocessed) continue; + if (!reprocessed) continue; - // Generate safe filename - use folder name for README.md files - let fileName: string; - if (path.basename(file.filePath) === 'README.md') { - // Use parent folder name for README files - const parentDir = path.basename(path.dirname(file.filePath)); - fileName = generateSafeFileName(parentDir); - } else { - fileName = generateSafeFileName(file.title || file.filePath); - } + // Generate safe filename - use folder name for README.md files + let fileName: string; + if (path.basename(file.filePath) === 'README.md') { + // Use parent folder name for README files + const parentDir = path.basename(path.dirname(file.filePath)); + fileName = generateSafeFileName(parentDir); + } else { + fileName = generateSafeFileName(file.title || file.filePath); + } - const outputPath = path.join(docsDir, `${fileName}.txt`); + const outputPath = path.join(docsDir, `${fileName}.txt`); - // Use the reprocessed content directly without adding metadata header - let txtContent = reprocessed.content || file.content; // Use reprocessed content with full URLs + // Use the reprocessed content directly without adding metadata header + let txtContent = reprocessed.content || file.content; // Use reprocessed content with full URLs - fs.writeFileSync(outputPath, txtContent, 'utf8'); - } + fs.writeFileSync(outputPath, txtContent, 'utf8'); + } } /** @@ -300,23 +309,34 @@ async function generateIndividualTxtFiles( * @param fileMapping - Mapping of source files to generated filenames * @returns Generated navigation content */ -async function generateSmallVersionHierarchical(language: Language, baseDir: string, config: DocusaurusConfig, fileMapping: Map): Promise { - const langName = LANGUAGE_NAMES[language]; - // Remove trailing slash from URL and ensure baseUrl starts with slash - const cleanUrl = config.url.replace(/\/$/, ''); - const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl; - const fullBaseUrl = `${cleanUrl}${cleanBaseUrl}`; - - let content = `# Teams SDK - ${langName} Documentation\n\n`; - content += COMMON_OVERALL_SUMMARY(language) + '\n\n'; - - // Get hierarchical structure - const hierarchical = getHierarchicalFiles(baseDir, `main/${language}`); - - // Add Language-specific Documentation - content += renderHierarchicalStructure(hierarchical.language, fullBaseUrl, language, fileMapping, 0); - - return content; +async function generateSmallVersionHierarchical( + language: Language, + baseDir: string, + config: DocusaurusConfig, + fileMapping: Map +): Promise { + const langName = LANGUAGE_NAMES[language]; + // Remove trailing slash from URL and ensure baseUrl starts with slash + const cleanUrl = config.url.replace(/\/$/, ''); + const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl; + const fullBaseUrl = `${cleanUrl}${cleanBaseUrl}`; + + let content = `# Teams SDK - ${langName} Documentation\n\n`; + content += COMMON_OVERALL_SUMMARY(language) + '\n\n'; + + // Get hierarchical structure + const hierarchical = getHierarchicalFiles(baseDir, `main/${language}`); + + // Add Language-specific Documentation + content += renderHierarchicalStructure( + hierarchical.language, + fullBaseUrl, + language, + fileMapping, + 0 + ); + + return content; } /** @@ -328,130 +348,142 @@ async function generateSmallVersionHierarchical(language: Language, baseDir: str * @param indentLevel - Current indentation level (0 = section headers, 1+ = bullet points) * @returns Rendered content with proper hierarchy */ -function renderHierarchicalStructure(structure: { [key: string]: FolderStructure }, baseUrl: string, language: Language, fileMapping: Map, indentLevel: number = 0): string { - let content = ''; +function renderHierarchicalStructure( + structure: { [key: string]: FolderStructure }, + baseUrl: string, + language: Language, + fileMapping: Map, + indentLevel: number = 0 +): string { + let content = ''; + + // Helper function for folder name formatting + function formatFolderName(name: string): string { + return name.replace(/[-_]/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()); + } + + // Convert structure to sorted array + const folders = Object.entries(structure) + .map(([key, value]) => ({ key, ...value })) + .sort((a, b) => { + const orderA = a.order || 999; + const orderB = b.order || 999; + if (orderA !== orderB) return orderA - orderB; + return a.key.localeCompare(b.key); + }); - // Helper function for folder name formatting - function formatFolderName(name: string): string { - return name.replace(/[-_]/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()); - } + for (const folder of folders) { + // Check if this folder has any content (files or children) + const hasFiles = folder.files && folder.files.length > 0; + const hasChildren = folder.children && Object.keys(folder.children).length > 0; + const hasContent = hasFiles || hasChildren; + + if (hasContent) { + // Check if this folder has a README file to make the header clickable + const readmeFile = hasFiles + ? folder.files.find((f) => path.basename(f.path) === 'README.md') + : null; + const displayTitle = + folder.title && folder.title !== folder.key ? folder.title : formatFolderName(folder.key); + + // Generate indent for nested folders (use spaces, 2 per level) + const indent = ' '.repeat(indentLevel); + + if (readmeFile) { + // Make folder header clickable by linking to the README + let folderFileName: string; + if (fileMapping && fileMapping.has(readmeFile.path)) { + folderFileName = fileMapping.get(readmeFile.path)!; + } else { + folderFileName = generateSafeFileName(folder.key); + } - // Convert structure to sorted array - const folders = Object.entries(structure) - .map(([key, value]) => ({ key, ...value })) - .sort((a, b) => { - const orderA = a.order || 999; - const orderB = b.order || 999; - if (orderA !== orderB) return orderA - orderB; - return a.key.localeCompare(b.key); - }); - - for (const folder of folders) { - // Check if this folder has any content (files or children) - const hasFiles = folder.files && folder.files.length > 0; - const hasChildren = folder.children && Object.keys(folder.children).length > 0; - const hasContent = hasFiles || hasChildren; - - if (hasContent) { - // Check if this folder has a README file to make the header clickable - const readmeFile = hasFiles - ? folder.files.find((f) => path.basename(f.path) === 'README.md') - : null; - const displayTitle = - folder.title && folder.title !== folder.key ? folder.title : formatFolderName(folder.key); - - // Generate indent for nested folders (use spaces, 2 per level) - const indent = ' '.repeat(indentLevel); - - if (readmeFile) { - // Make folder header clickable by linking to the README - let folderFileName: string; - if (fileMapping && fileMapping.has(readmeFile.path)) { - folderFileName = fileMapping.get(readmeFile.path)!; - } else { - folderFileName = generateSafeFileName(folder.key); - } - - // Use ### header for top-level sections (indentLevel 0), bullet points for nested - if (indentLevel === 0) { - content += `### [${displayTitle}](${baseUrl}llms_docs/docs_${language}/${folderFileName}.txt)\n\n`; - } else { - content += `${indent}- [${displayTitle}](${baseUrl}llms_docs/docs_${language}/${folderFileName}.txt)`; - } - - // Add summary from README if available - try { - const readmeContent = readFileUtf8Normalized(readmeFile.path); - const { frontmatter } = FrontmatterParser.extract(readmeContent); - const summary = frontmatter.summary; - if (summary) { - if (indentLevel === 0) { - content += `${summary}\n\n`; - } else { - content += `: ${summary}`; - } - } - } catch (error) { - // Ignore errors reading README summary - } - - if (indentLevel > 0) { - content += '\n'; - } - } else { - // No README - if (indentLevel === 0) { - content += `### ${displayTitle}\n\n`; - } else { - content += `${indent}- ${displayTitle}\n`; - } - } + // Use ### header for top-level sections (indentLevel 0), bullet points for nested + if (indentLevel === 0) { + content += `### [${displayTitle}](${baseUrl}llms_docs/docs_${language}/${folderFileName}.txt)\n\n`; + } else { + content += `${indent}- [${displayTitle}](${baseUrl}llms_docs/docs_${language}/${folderFileName}.txt)`; + } - // Add files in this folder (sorted by order), excluding README - if (hasFiles) { - const sortedFiles = [...folder.files] - .filter((f) => path.basename(f.path) !== 'README.md') // Exclude README since it's now the header link - .sort((a, b) => { - const orderA = a.order || 999; - const orderB = b.order || 999; - if (orderA !== orderB) return orderA - orderB; - return a.name.localeCompare(b.name); - }); - - for (const file of sortedFiles) { - // Use file mapping to get the correct generated filename - let fileName: string; - if (fileMapping && fileMapping.has(file.path)) { - fileName = fileMapping.get(file.path)!; - } else { - fileName = generateSafeFileName(file.title || file.name); - } - - const summary = extractSummaryFromFile(file.path); - - // Files are always indented one level deeper than their parent folder - const fileIndent = ' '.repeat(indentLevel + 1); - content += `${fileIndent}- [${file.title}](${baseUrl}llms_docs/docs_${language}/${fileName}.txt)`; - if (summary) { - content += `: ${summary}`; - } - content += '\n'; - } + // Add summary from README if available + try { + const readmeContent = readFileUtf8Normalized(readmeFile.path); + const { frontmatter } = FrontmatterParser.extract(readmeContent); + const summary = frontmatter.summary; + if (summary) { + if (indentLevel === 0) { + content += `${summary}\n\n`; + } else { + content += `: ${summary}`; } + } + } catch (error) { + // Ignore errors reading README summary + } - // Recursively render children with increased indent - if (hasChildren) { - content += renderHierarchicalStructure(folder.children, baseUrl, language, fileMapping, indentLevel + 1); - } + if (indentLevel > 0) { + content += '\n'; + } + } else { + // No README + if (indentLevel === 0) { + content += `### ${displayTitle}\n\n`; + } else { + content += `${indent}- ${displayTitle}\n`; + } + } - // Add spacing after top-level sections - if (indentLevel === 0) { - content += '\n'; - } + // Add files in this folder (sorted by order), excluding README + if (hasFiles) { + const sortedFiles = [...folder.files] + .filter((f) => path.basename(f.path) !== 'README.md') // Exclude README since it's now the header link + .sort((a, b) => { + const orderA = a.order || 999; + const orderB = b.order || 999; + if (orderA !== orderB) return orderA - orderB; + return a.name.localeCompare(b.name); + }); + + for (const file of sortedFiles) { + // Use file mapping to get the correct generated filename + let fileName: string; + if (fileMapping && fileMapping.has(file.path)) { + fileName = fileMapping.get(file.path)!; + } else { + fileName = generateSafeFileName(file.title || file.name); + } + + const summary = extractSummaryFromFile(file.path); + + // Files are always indented one level deeper than their parent folder + const fileIndent = ' '.repeat(indentLevel + 1); + content += `${fileIndent}- [${file.title}](${baseUrl}llms_docs/docs_${language}/${fileName}.txt)`; + if (summary) { + content += `: ${summary}`; + } + content += '\n'; } + } + + // Recursively render children with increased indent + if (hasChildren) { + content += renderHierarchicalStructure( + folder.children, + baseUrl, + language, + fileMapping, + indentLevel + 1 + ); + } + + // Add spacing after top-level sections + if (indentLevel === 0) { + content += '\n'; + } } + } - return content; + return content; } /** @@ -460,40 +492,40 @@ function renderHierarchicalStructure(structure: { [key: string]: FolderStructure * @returns File summary or empty string */ function extractSummaryFromFile(filePath: string): string { - try { - const fileContent = readFileUtf8Normalized(filePath); - - // First check for summary in frontmatter - const { frontmatter, content } = FrontmatterParser.extract(fileContent); - const summary = frontmatter.summary; - if (summary && typeof summary === 'string') { - return summary; - } + try { + const fileContent = readFileUtf8Normalized(filePath); + + // First check for summary in frontmatter + const { frontmatter, content } = FrontmatterParser.extract(fileContent); + const summary = frontmatter.summary; + if (summary && typeof summary === 'string') { + return summary; + } - // Remove HTML comments before extracting summary - // Generated .mdx files contain AUTO-GENERATED warnings that shouldn't appear in summaries - const cleanContent = content.replace(//g, ''); - - // Fallback to extracting first meaningful paragraph if no summary in frontmatter - const paragraphs = cleanContent.split('\n\n'); - for (const paragraph of paragraphs) { - const clean = paragraph - .replace(/#+\s*/g, '') // Remove headers - .replace(/\*\*(.+?)\*\*/g, '$1') // Remove bold - .replace(/\*(.+?)\*/g, '$1') // Remove italic - .replace(/`(.+?)`/g, '$1') // Remove inline code - .replace(/\[(.+?)\]\(.+?\)/g, '$1') // Remove links, keep text - .trim(); - - if (clean.length > 20 && !clean.startsWith('```') && !clean.startsWith('import')) { - return clean.length > 100 ? clean.substring(0, 100) + '...' : clean; - } - } - } catch (error) { - // Ignore file read errors + // Remove HTML comments before extracting summary + // Generated .mdx files contain AUTO-GENERATED warnings that shouldn't appear in summaries + const cleanContent = content.replace(//g, ''); + + // Fallback to extracting first meaningful paragraph if no summary in frontmatter + const paragraphs = cleanContent.split('\n\n'); + for (const paragraph of paragraphs) { + const clean = paragraph + .replace(/#+\s*/g, '') // Remove headers + .replace(/\*\*(.+?)\*\*/g, '$1') // Remove bold + .replace(/\*(.+?)\*/g, '$1') // Remove italic + .replace(/`(.+?)`/g, '$1') // Remove inline code + .replace(/\[(.+?)\]\(.+?\)/g, '$1') // Remove links, keep text + .trim(); + + if (clean.length > 20 && !clean.startsWith('```') && !clean.startsWith('import')) { + return clean.length > 100 ? clean.substring(0, 100) + '...' : clean; + } } + } catch (error) { + // Ignore file read errors + } - return ''; + return ''; } /** @@ -503,28 +535,32 @@ function extractSummaryFromFile(filePath: string): string { * @param baseDir - Base directory path * @returns Generated content */ -async function generateFullVersion(language: Language, processedFiles: ProcessedFile[], baseDir: string): Promise { - const langName = LANGUAGE_NAMES[language] - let content = `# Teams SDK - ${langName} Documentation (Complete)\n\n`; - content += COMMON_OVERALL_SUMMARY(language) + '\n\n'; - - // Group files by section - const sections = groupFilesBySection(processedFiles, baseDir); - - // Process all sections - for (const [sectionName, files] of Object.entries(sections)) { - if (!files || files.length === 0) continue; - - content += `## ${formatSectionName(sectionName)}\n\n`; - - for (const file of files) { - if (file.content) { - content += `### ${file.title}\n\n${file.content}\n\n---\n\n`; - } - } +async function generateFullVersion( + language: Language, + processedFiles: ProcessedFile[], + baseDir: string +): Promise { + const langName = LANGUAGE_NAMES[language]; + let content = `# Teams SDK - ${langName} Documentation (Complete)\n\n`; + content += COMMON_OVERALL_SUMMARY(language) + '\n\n'; + + // Group files by section + const sections = groupFilesBySection(processedFiles, baseDir); + + // Process all sections + for (const [sectionName, files] of Object.entries(sections)) { + if (!files || files.length === 0) continue; + + content += `## ${formatSectionName(sectionName)}\n\n`; + + for (const file of files) { + if (file.content) { + content += `### ${file.title}\n\n${file.content}\n\n---\n\n`; + } } + } - return content; + return content; } /** @@ -533,56 +569,59 @@ async function generateFullVersion(language: Language, processedFiles: Processed * @param baseDir - Base directory path * @returns Grouped files by section */ -function groupFilesBySection(processedFiles: ProcessedFile[], baseDir: string): { [key: string]: ProcessedFile[] } { - const sections: { [key: string]: ProcessedFile[] } = { - main: [], - gettingStarted: [], - essentials: [], - inDepthGuides: [], - migrations: [], - }; - - for (const file of processedFiles) { - const relativePath = path.relative(path.join(baseDir, 'docs'), file.filePath); - - if (relativePath.startsWith('main/')) { - sections.main.push(file); - } else if (relativePath.includes('getting-started/')) { - sections.gettingStarted.push(file); - } else if (relativePath.includes('essentials/')) { - sections.essentials.push(file); - } else if (relativePath.includes('in-depth-guides/')) { - sections.inDepthGuides.push(file); - } else if (relativePath.includes('migrations/')) { - sections.migrations.push(file); - } else { - // Create dynamic section based on directory - const parts = relativePath.split('/'); - if (parts.length > 1) { - const sectionKey = parts[1].replace(/[^a-zA-Z0-9]/g, ''); - if (!sections[sectionKey]) { - sections[sectionKey] = []; - } - sections[sectionKey].push(file); - } +function groupFilesBySection( + processedFiles: ProcessedFile[], + baseDir: string +): { [key: string]: ProcessedFile[] } { + const sections: { [key: string]: ProcessedFile[] } = { + main: [], + gettingStarted: [], + essentials: [], + inDepthGuides: [], + migrations: [], + }; + + for (const file of processedFiles) { + const relativePath = path.relative(path.join(baseDir, 'docs'), file.filePath); + + if (relativePath.startsWith('main/')) { + sections.main.push(file); + } else if (relativePath.includes('getting-started/')) { + sections.gettingStarted.push(file); + } else if (relativePath.includes('essentials/')) { + sections.essentials.push(file); + } else if (relativePath.includes('in-depth-guides/')) { + sections.inDepthGuides.push(file); + } else if (relativePath.includes('migrations/')) { + sections.migrations.push(file); + } else { + // Create dynamic section based on directory + const parts = relativePath.split('/'); + if (parts.length > 1) { + const sectionKey = parts[1].replace(/[^a-zA-Z0-9]/g, ''); + if (!sections[sectionKey]) { + sections[sectionKey] = []; } + sections[sectionKey].push(file); + } } + } - // Sort files within each section by sidebar position - for (const sectionFiles of Object.values(sections)) { - sectionFiles.sort((a, b) => { - const posA = a.sidebarPosition || 999; - const posB = b.sidebarPosition || 999; + // Sort files within each section by sidebar position + for (const sectionFiles of Object.values(sections)) { + sectionFiles.sort((a, b) => { + const posA = a.sidebarPosition || 999; + const posB = b.sidebarPosition || 999; - if (posA !== posB) { - return posA - posB; - } + if (posA !== posB) { + return posA - posB; + } - return (a.title || '').localeCompare(b.title || ''); - }); - } + return (a.title || '').localeCompare(b.title || ''); + }); + } - return sections; + return sections; } /** @@ -591,16 +630,16 @@ function groupFilesBySection(processedFiles: ProcessedFile[], baseDir: string): * @returns Safe filename */ function generateSafeFileName(title: string): string { - return ( - title - .toLowerCase() - .replace(/[^a-z0-9\s-]/g, '') // Remove special characters - .replace(/\s+/g, '-') // Replace spaces with hyphens - .replace(/-+/g, '-') // Replace multiple hyphens with single - .replace(/^-|-$/g, '') // Remove leading/trailing hyphens - .substring(0, 50) || // Limit length - 'untitled' - ); + return ( + title + .toLowerCase() + .replace(/[^a-z0-9\s-]/g, '') // Remove special characters + .replace(/\s+/g, '-') // Replace spaces with hyphens + .replace(/-+/g, '-') // Replace multiple hyphens with single + .replace(/^-|-$/g, '') // Remove leading/trailing hyphens + .substring(0, 50) || // Limit length + 'untitled' + ); } /** @@ -609,21 +648,21 @@ function generateSafeFileName(title: string): string { * @returns Formatted section name */ function formatSectionName(sectionName: string): string { - const nameMap: { [key: string]: string } = { - main: 'Main Documentation', - gettingStarted: 'Getting Started', - essentials: 'Essentials', - inDepthGuides: 'In-Depth Guides', - migrations: 'Migrations', - }; - - return ( - nameMap[sectionName] || - sectionName - .replace(/([A-Z])/g, ' $1') // Add spaces before capitals - .replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter - .trim() - ); + const nameMap: { [key: string]: string } = { + main: 'Main Documentation', + gettingStarted: 'Getting Started', + essentials: 'Essentials', + inDepthGuides: 'In-Depth Guides', + migrations: 'Migrations', + }; + + return ( + nameMap[sectionName] || + sectionName + .replace(/([A-Z])/g, ' $1') // Add spaces before capitals + .replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter + .trim() + ); } /** @@ -632,16 +671,16 @@ function formatSectionName(sectionName: string): string { * @returns Formatted string */ function formatBytes(bytes: number): string { - if (bytes === 0) return '0 B'; - const k = 1024; - const sizes = ['B', 'KB', 'MB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } // Run the generator if this file is executed directly if (require.main === module) { - generateLlmsTxt(); + generateLlmsTxt(); } export { generateLlmsTxt }; diff --git a/teams.md/scripts/lib/content-processor.ts b/teams.md/scripts/lib/content-processor.ts index edadb408c..11bcd4879 100644 --- a/teams.md/scripts/lib/content-processor.ts +++ b/teams.md/scripts/lib/content-processor.ts @@ -5,23 +5,23 @@ import { FrontmatterParser } from './frontmatter-parser'; import readFileUtf8Normalized from '../../src/utils/readFileUtf8Normalized'; interface ProcessedContent { - title: string; - content: string; - frontmatter: { [key: string]: any }; - filePath: string; - sidebarPosition: number; - relativeUrl: string; + title: string; + content: string; + frontmatter: { [key: string]: any }; + filePath: string; + sidebarPosition: number; + relativeUrl: string; } interface ParsedMarkdown { - title: string; - content: string; - frontmatter: { [key: string]: any }; + title: string; + content: string; + frontmatter: { [key: string]: any }; } interface DocusaurusConfig { - url: string; - baseUrl: string; + url: string; + baseUrl: string; } /** @@ -30,35 +30,39 @@ interface DocusaurusConfig { * @returns True if file should be ignored due to section filtering */ export function shouldIgnoreFileBySection(filePath: string): boolean { - // Get the directory path - let currentDir = path.dirname(filePath); - - // Walk up the directory tree looking for README.md or index.mdx files - while (currentDir && currentDir !== path.dirname(currentDir)) { - const readmePath = path.join(currentDir, 'README.md'); - const indexPath = path.join(currentDir, 'index.mdx'); - - // Check README.md first, then index.mdx - const indexFilePath = fs.existsSync(readmePath) ? readmePath : (fs.existsSync(indexPath) ? indexPath : null); - - if (indexFilePath) { - try { - const indexContent = readFileUtf8Normalized(indexFilePath); - const indexFrontmatter = FrontmatterParser.extract(indexContent).frontmatter; - // Only ignore entire section if index file has 'llms: ignore' (not 'ignore-file') - if (indexFrontmatter.llms === 'ignore' || indexFrontmatter.llms === false) { - return true; - } - } catch (error) { - // Ignore errors reading index file - } + // Get the directory path + let currentDir = path.dirname(filePath); + + // Walk up the directory tree looking for README.md or index.mdx files + while (currentDir && currentDir !== path.dirname(currentDir)) { + const readmePath = path.join(currentDir, 'README.md'); + const indexPath = path.join(currentDir, 'index.mdx'); + + // Check README.md first, then index.mdx + const indexFilePath = fs.existsSync(readmePath) + ? readmePath + : fs.existsSync(indexPath) + ? indexPath + : null; + + if (indexFilePath) { + try { + const indexContent = readFileUtf8Normalized(indexFilePath); + const indexFrontmatter = FrontmatterParser.extract(indexContent).frontmatter; + // Only ignore entire section if index file has 'llms: ignore' (not 'ignore-file') + if (indexFrontmatter.llms === 'ignore' || indexFrontmatter.llms === false) { + return true; } - - // Move up one directory - currentDir = path.dirname(currentDir); + } catch (error) { + // Ignore errors reading index file + } } - return false; + // Move up one directory + currentDir = path.dirname(currentDir); + } + + return false; } /** @@ -72,56 +76,71 @@ export function shouldIgnoreFileBySection(filePath: string): boolean { * @returns Processed content with title, content, and metadata */ export async function processContent( - filePath: string, - baseDir: string, - includeCodeBlocks: boolean = false, - fileMapping: Map | null = null, - config: DocusaurusConfig | null = null, - language: string | null = null + filePath: string, + baseDir: string, + includeCodeBlocks: boolean = false, + fileMapping: Map | null = null, + config: DocusaurusConfig | null = null, + language: string | null = null ): Promise { - try { - if (!fs.existsSync(filePath)) { - console.warn(`āš ļø File not found: ${filePath}`); - return { title: '', content: '', frontmatter: {}, filePath, sidebarPosition: 999, relativeUrl: '' }; - } + try { + if (!fs.existsSync(filePath)) { + console.warn(`āš ļø File not found: ${filePath}`); + return { + title: '', + content: '', + frontmatter: {}, + filePath, + sidebarPosition: 999, + relativeUrl: '', + }; + } const rawContent = readFileUtf8Normalized(filePath); - // Check if this file should be excluded from LLM output - if (FrontmatterParser.shouldIgnore(rawContent)) { - return null; // Return null to indicate this file should be skipped - } - - // Check if language filtering is enabled and if this file is for a different language - const { frontmatter: earlyFrontmatter } = FrontmatterParser.extract(rawContent); - if (language && earlyFrontmatter.languages) { - const fileLanguages = Array.isArray(earlyFrontmatter.languages) - ? earlyFrontmatter.languages - : [earlyFrontmatter.languages]; - if (!fileLanguages.includes(language)) { - return null; // Skip this file as it's not for the current language - } - } - - const { title, content, frontmatter } = await parseMarkdownContent(rawContent, baseDir, includeCodeBlocks, filePath, fileMapping, config, language); + // Check if this file should be excluded from LLM output + if (FrontmatterParser.shouldIgnore(rawContent)) { + return null; // Return null to indicate this file should be skipped + } - // Check if this file should be ignored due to section-wide filtering - if (shouldIgnoreFileBySection(filePath)) { - return null; // Return null to indicate this file should be skipped - } + // Check if language filtering is enabled and if this file is for a different language + const { frontmatter: earlyFrontmatter } = FrontmatterParser.extract(rawContent); + if (language && earlyFrontmatter.languages) { + const fileLanguages = Array.isArray(earlyFrontmatter.languages) + ? earlyFrontmatter.languages + : [earlyFrontmatter.languages]; + if (!fileLanguages.includes(language)) { + return null; // Skip this file as it's not for the current language + } + } - return { - title: title, - content: content || '', - frontmatter: frontmatter || {}, - filePath, - sidebarPosition: (frontmatter.sidebar_position as number) || 999, - relativeUrl: generateRelativeUrl(filePath, baseDir) - }; - } catch (error) { - console.error(`āŒ Error processing file ${filePath}:`, (error as Error).message); - throw error; // Re-throw to fail the build + const { title, content, frontmatter } = await parseMarkdownContent( + rawContent, + baseDir, + includeCodeBlocks, + filePath, + fileMapping, + config, + language + ); + + // Check if this file should be ignored due to section-wide filtering + if (shouldIgnoreFileBySection(filePath)) { + return null; // Return null to indicate this file should be skipped } + + return { + title: title, + content: content || '', + frontmatter: frontmatter || {}, + filePath, + sidebarPosition: (frontmatter.sidebar_position as number) || 999, + relativeUrl: generateRelativeUrl(filePath, baseDir), + }; + } catch (error) { + console.error(`āŒ Error processing file ${filePath}:`, (error as Error).message); + throw error; // Re-throw to fail the build + } } /** @@ -136,46 +155,46 @@ export async function processContent( * @returns Parsed title, content, and frontmatter */ async function parseMarkdownContent( - rawContent: string, - baseDir: string, - includeCodeBlocks: boolean, - filePath: string, - fileMapping: Map | null, - config: DocusaurusConfig | null, - language: string | null + rawContent: string, + baseDir: string, + includeCodeBlocks: boolean, + filePath: string, + fileMapping: Map | null, + config: DocusaurusConfig | null, + language: string | null ): Promise { - // Extract and remove frontmatter using enhanced parser with js-yaml - const { frontmatter, content: contentWithoutFrontmatter } = FrontmatterParser.extract(rawContent); - let content = contentWithoutFrontmatter; - - // Extract title from first H1 (always required) - const h1Match = content.match(/^#\s+(.+)$/m); - if (!h1Match) { - throw new Error(`No # header found in file: ${filePath}`); - } - const title = h1Match[1].trim(); - - // Remove import statements - content = content.replace(/^import\s+.*$/gm, ''); - - // Process FileCodeBlock components if requested - if (includeCodeBlocks) { - content = await processFileCodeBlocks(content, baseDir); - } else { - // Remove FileCodeBlock components for small version - content = content.replace(//g, '[Code example removed for brevity]'); - } - - // Clean up MDX-specific syntax while preserving markdown - content = cleanMdxSyntax(content); - - // Fix internal relative links - content = fixInternalLinks(content, filePath, fileMapping, config, language); - - // Remove excessive whitespace - content = content.replace(/\n{3,}/g, '\n\n').trim(); - - return { title, content, frontmatter }; + // Extract and remove frontmatter using enhanced parser with js-yaml + const { frontmatter, content: contentWithoutFrontmatter } = FrontmatterParser.extract(rawContent); + let content = contentWithoutFrontmatter; + + // Extract title from first H1 (always required) + const h1Match = content.match(/^#\s+(.+)$/m); + if (!h1Match) { + throw new Error(`No # header found in file: ${filePath}`); + } + const title = h1Match[1].trim(); + + // Remove import statements + content = content.replace(/^import\s+.*$/gm, ''); + + // Process FileCodeBlock components if requested + if (includeCodeBlocks) { + content = await processFileCodeBlocks(content, baseDir); + } else { + // Remove FileCodeBlock components for small version + content = content.replace(//g, '[Code example removed for brevity]'); + } + + // Clean up MDX-specific syntax while preserving markdown + content = cleanMdxSyntax(content); + + // Fix internal relative links + content = fixInternalLinks(content, filePath, fileMapping, config, language); + + // Remove excessive whitespace + content = content.replace(/\n{3,}/g, '\n\n').trim(); + + return { title, content, frontmatter }; } /** @@ -185,27 +204,27 @@ async function parseMarkdownContent( * @returns Content with FileCodeBlocks replaced by actual code */ async function processFileCodeBlocks(content: string, baseDir: string): Promise { - const fileCodeBlockRegex = /]+)\/>/g; - let processedContent = content; - let match; - - while ((match = fileCodeBlockRegex.exec(content)) !== null) { - const attributes = parseAttributes(match[1]); - const { src, lang } = attributes; - - if (src) { - try { - const codeContent = await loadCodeFile(src, baseDir); - const codeBlock = `\`\`\`${lang || 'typescript'}\n${codeContent}\n\`\`\``; - processedContent = processedContent.replace(match[0], codeBlock); - } catch (error) { - console.warn(`āš ļø Could not load code file ${src}:`, (error as Error).message); - processedContent = processedContent.replace(match[0], `[Code file not found: ${src}]`); - } - } + const fileCodeBlockRegex = /]+)\/>/g; + let processedContent = content; + let match; + + while ((match = fileCodeBlockRegex.exec(content)) !== null) { + const attributes = parseAttributes(match[1]); + const { src, lang } = attributes; + + if (src) { + try { + const codeContent = await loadCodeFile(src, baseDir); + const codeBlock = `\`\`\`${lang || 'typescript'}\n${codeContent}\n\`\`\``; + processedContent = processedContent.replace(match[0], codeBlock); + } catch (error) { + console.warn(`āš ļø Could not load code file ${src}:`, (error as Error).message); + processedContent = processedContent.replace(match[0], `[Code file not found: ${src}]`); + } } + } - return processedContent; + return processedContent; } /** @@ -215,19 +234,19 @@ async function processFileCodeBlocks(content: string, baseDir: string): Promise< * @returns Code content */ async function loadCodeFile(src: string, baseDir: string): Promise { - // Convert src path to local file path - let filePath: string; - if (src.startsWith('/')) { - filePath = path.join(baseDir, 'static', src.substring(1)); - } else { - filePath = path.join(baseDir, 'static', src); - } - - if (fs.existsSync(filePath)) { - return readFileUtf8Normalized(filePath).trim(); - } else { - throw new Error(`File not found: ${filePath}`); - } + // Convert src path to local file path + let filePath: string; + if (src.startsWith('/')) { + filePath = path.join(baseDir, 'static', src.substring(1)); + } else { + filePath = path.join(baseDir, 'static', src); + } + + if (fs.existsSync(filePath)) { + return readFileUtf8Normalized(filePath).trim(); + } else { + throw new Error(`File not found: ${filePath}`); + } } /** @@ -236,15 +255,15 @@ async function loadCodeFile(src: string, baseDir: string): Promise { * @returns Parsed attributes */ function parseAttributes(attributeString: string): { [key: string]: string } { - const attributes: { [key: string]: string } = {}; - const regex = /(\w+)=["']([^"']+)["']/g; - let match; + const attributes: { [key: string]: string } = {}; + const regex = /(\w+)=["']([^"']+)["']/g; + let match; - while ((match = regex.exec(attributeString)) !== null) { - attributes[match[1]] = match[2]; - } + while ((match = regex.exec(attributeString)) !== null) { + attributes[match[1]] = match[2]; + } - return attributes; + return attributes; } /** @@ -253,53 +272,52 @@ function parseAttributes(attributeString: string): { [key: string]: string } { * @returns Cleaned content */ function cleanMdxSyntax(content: string): string { - let cleaned = content; - - // Remove HTML comments from llms.txt output - // Generated .mdx files contain AUTO-GENERATED warnings as HTML comments for developers, - // but these developer notes don't belong in AI-friendly documentation files. - // Note: Section markers () in .incl.md source files are processed - // earlier by generate-language-docs.ts and never appear in generated .mdx files. - cleaned = cleaned.replace(//g, ''); - - // Remove JSX components (except code blocks which are handled separately) - cleaned = cleaned.replace(/<\/?[A-Z][^>]*>/g, ''); - - // Remove empty JSX fragments - cleaned = cleaned.replace(/<>\s*<\/>/g, ''); - - // Remove JSX expressions but keep the content if it's simple text - // IMPORTANT: Don't process content inside code blocks (```) - const codeBlockRegex = /```[\s\S]*?```/g; - const codeBlocks: string[] = []; - - // Extract code blocks temporarily - cleaned = cleaned.replace(codeBlockRegex, (match) => { - codeBlocks.push(match); - return `___CODE_BLOCK_${codeBlocks.length - 1}___`; - }); - - // Now remove JSX expressions outside code blocks - cleaned = cleaned.replace(/\{([^{}]+)\}/g, (match, expr) => { - // Keep simple text expressions, remove complex ones - if (expr.includes('(') || expr.includes('.') || expr.includes('[')) { - return ''; - } - return expr; - }); + let cleaned = content; + + // Remove HTML comments from llms.txt output + // Generated .mdx files contain AUTO-GENERATED warnings as HTML comments for developers, + // but these developer notes don't belong in AI-friendly documentation files. + // Note: Section markers () in .incl.md source files are processed + // earlier by generate-language-docs.ts and never appear in generated .mdx files. + cleaned = cleaned.replace(//g, ''); + + // Remove JSX components (except code blocks which are handled separately) + cleaned = cleaned.replace(/<\/?[A-Z][^>]*>/g, ''); + + // Remove empty JSX fragments + cleaned = cleaned.replace(/<>\s*<\/>/g, ''); + + // Remove JSX expressions but keep the content if it's simple text + // IMPORTANT: Don't process content inside code blocks (```) + const codeBlockRegex = /```[\s\S]*?```/g; + const codeBlocks: string[] = []; + + // Extract code blocks temporarily + cleaned = cleaned.replace(codeBlockRegex, (match) => { + codeBlocks.push(match); + return `___CODE_BLOCK_${codeBlocks.length - 1}___`; + }); + + // Now remove JSX expressions outside code blocks + cleaned = cleaned.replace(/\{([^{}]+)\}/g, (match, expr) => { + // Keep simple text expressions, remove complex ones + if (expr.includes('(') || expr.includes('.') || expr.includes('[')) { + return ''; + } + return expr; + }); - // Restore code blocks - cleaned = cleaned.replace(/___CODE_BLOCK_(\d+)___/g, (match, index) => { - return codeBlocks[parseInt(index)]; - }); + // Restore code blocks + cleaned = cleaned.replace(/___CODE_BLOCK_(\d+)___/g, (match, index) => { + return codeBlocks[parseInt(index)]; + }); - // Clean up multiple empty lines - cleaned = cleaned.replace(/\n\s*\n\s*\n/g, '\n\n'); + // Clean up multiple empty lines + cleaned = cleaned.replace(/\n\s*\n\s*\n/g, '\n\n'); - return cleaned; + return cleaned; } - /** * Generates a relative URL for a documentation file * @param filePath - Full path to the file @@ -307,26 +325,26 @@ function cleanMdxSyntax(content: string): string { * @returns Relative URL for the file */ export function generateRelativeUrl(filePath: string, baseDir: string): string { - const relativePath = path.relative(path.join(baseDir, 'docs'), filePath); - - // Convert file path to URL format - let url = relativePath - .replace(/\\/g, '/') // Convert Windows paths - .replace(/\.mdx?$/, '') // Remove .md/.mdx extension - .replace(/\/README$/i, '') // Remove /README from end - .replace(/\/index$/i, ''); // Remove /index from end - - // Add leading slash - if (!url.startsWith('/')) { - url = '/' + url; - } - - // Handle empty URL (root README) - if (url === '/') { - url = ''; - } - - return url; + const relativePath = path.relative(path.join(baseDir, 'docs'), filePath); + + // Convert file path to URL format + let url = relativePath + .replace(/\\/g, '/') // Convert Windows paths + .replace(/\.mdx?$/, '') // Remove .md/.mdx extension + .replace(/\/README$/i, '') // Remove /README from end + .replace(/\/index$/i, ''); // Remove /index from end + + // Add leading slash + if (!url.startsWith('/')) { + url = '/' + url; + } + + // Handle empty URL (root README) + if (url === '/') { + url = ''; + } + + return url; } /** @@ -339,85 +357,90 @@ export function generateRelativeUrl(filePath: string, baseDir: string): string { * @returns Content with fixed links */ export function fixInternalLinks( - content: string, - currentFilePath: string, - fileMapping: Map | null, - config: DocusaurusConfig | null, - language: string | null + content: string, + currentFilePath: string, + fileMapping: Map | null, + config: DocusaurusConfig | null, + language: string | null ): string { - // Pattern to match markdown links: [text](link) - const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; + // Pattern to match markdown links: [text](link) + const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; - return content.replace(linkRegex, (match, text, link) => { - // Skip external links (http/https/mailto/etc) - if (link.startsWith('http') || link.startsWith('mailto') || link.startsWith('#')) { - return match; - } + return content.replace(linkRegex, (match, text, link) => { + // Skip external links (http/https/mailto/etc) + if (link.startsWith('http') || link.startsWith('mailto') || link.startsWith('#')) { + return match; + } - // Skip absolute paths starting with / - if (link.startsWith('/') && !link.startsWith('//')) { - return match; - } + // Skip absolute paths starting with / + if (link.startsWith('/') && !link.startsWith('//')) { + return match; + } - // Handle relative links - if (!link.includes('://')) { - // Remove any file extensions and anchors - const cleanLink = link.split('#')[0].replace(/\.(md|mdx)$/, ''); - - // If it's just a filename without path separators, it's likely a sibling file - if (!cleanLink.includes('/')) { - // Try to resolve using file mapping first - if (fileMapping) { - const currentDir = path.dirname(currentFilePath); - const possiblePath = path.join(currentDir, cleanLink + '.md'); - const possibleMdxPath = path.join(currentDir, cleanLink + '.mdx'); - - // Look for the file in the mapping - for (const [sourcePath, generatedName] of fileMapping.entries()) { - // Check exact path match or basename match - if (sourcePath === possiblePath || - sourcePath === possibleMdxPath || - path.basename(sourcePath, '.md') === cleanLink || - path.basename(sourcePath, '.mdx') === cleanLink) { - - // Generate full URL if config and language are provided - if (config && language) { - const cleanUrl = config.url.replace(/\/$/, ''); - const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl; - const fullBaseUrl = `${cleanUrl}${cleanBaseUrl}`; - return `[${text}](${fullBaseUrl}llms_docs/docs_${language}/${generatedName}.txt)`; - } else { - return `[${text}](${generatedName}.txt)`; - } - } - } - } - - // Fallback to simple conversion - const safeFileName = cleanLink - .toLowerCase() - .replace(/[^a-z0-9\s-]/g, '') - .replace(/\s+/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, '') - .substring(0, 50) - || 'untitled'; - - // Generate full URL if config and language are provided - if (config && language) { - const cleanUrl = config.url.replace(/\/$/, ''); - const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl; - const fullBaseUrl = `${cleanUrl}${cleanBaseUrl}`; - return `[${text}](${fullBaseUrl}llms_docs/docs_${language}/${safeFileName}.txt)`; - } else { - return `[${text}](${safeFileName}.txt)`; - } + // Handle relative links + if (!link.includes('://')) { + // Remove any file extensions and anchors + const cleanLink = link.split('#')[0].replace(/\.(md|mdx)$/, ''); + + // If it's just a filename without path separators, it's likely a sibling file + if (!cleanLink.includes('/')) { + // Try to resolve using file mapping first + if (fileMapping) { + const currentDir = path.dirname(currentFilePath); + const possiblePath = path.join(currentDir, cleanLink + '.md'); + const possibleMdxPath = path.join(currentDir, cleanLink + '.mdx'); + + // Look for the file in the mapping + for (const [sourcePath, generatedName] of fileMapping.entries()) { + // Check exact path match or basename match + if ( + sourcePath === possiblePath || + sourcePath === possibleMdxPath || + path.basename(sourcePath, '.md') === cleanLink || + path.basename(sourcePath, '.mdx') === cleanLink + ) { + // Generate full URL if config and language are provided + if (config && language) { + const cleanUrl = config.url.replace(/\/$/, ''); + const cleanBaseUrl = config.baseUrl.startsWith('/') + ? config.baseUrl + : '/' + config.baseUrl; + const fullBaseUrl = `${cleanUrl}${cleanBaseUrl}`; + return `[${text}](${fullBaseUrl}llms_docs/docs_${language}/${generatedName}.txt)`; + } else { + return `[${text}](${generatedName}.txt)`; + } } + } } - // Return original link if we can't process it - return match; - }); + // Fallback to simple conversion + const safeFileName = + cleanLink + .toLowerCase() + .replace(/[^a-z0-9\s-]/g, '') + .replace(/\s+/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, '') + .substring(0, 50) || 'untitled'; + + // Generate full URL if config and language are provided + if (config && language) { + const cleanUrl = config.url.replace(/\/$/, ''); + const cleanBaseUrl = config.baseUrl.startsWith('/') + ? config.baseUrl + : '/' + config.baseUrl; + const fullBaseUrl = `${cleanUrl}${cleanBaseUrl}`; + return `[${text}](${fullBaseUrl}llms_docs/docs_${language}/${safeFileName}.txt)`; + } else { + return `[${text}](${safeFileName}.txt)`; + } + } + } + + // Return original link if we can't process it + return match; + }); } /** @@ -427,22 +450,22 @@ export function fixInternalLinks( * @returns Content summary */ export function extractSummary(content: string, maxLength: number = 200): string { - // Remove markdown formatting for summary - let summary = content - .replace(/#+\s*/g, '') // Remove headers - .replace(/\*\*(.+?)\*\*/g, '$1') // Remove bold - .replace(/\*(.+?)\*/g, '$1') // Remove italic - .replace(/`(.+?)`/g, '$1') // Remove inline code - .replace(/\[(.+?)\]\(.+?\)/g, '$1') // Remove links, keep text - .trim(); - - // Get first paragraph - const firstParagraph = summary.split('\n\n')[0]; - - // Truncate if too long - if (firstParagraph.length > maxLength) { - return firstParagraph.substring(0, maxLength).trim() + '...'; - } - - return firstParagraph; + // Remove markdown formatting for summary + let summary = content + .replace(/#+\s*/g, '') // Remove headers + .replace(/\*\*(.+?)\*\*/g, '$1') // Remove bold + .replace(/\*(.+?)\*/g, '$1') // Remove italic + .replace(/`(.+?)`/g, '$1') // Remove inline code + .replace(/\[(.+?)\]\(.+?\)/g, '$1') // Remove links, keep text + .trim(); + + // Get first paragraph + const firstParagraph = summary.split('\n\n')[0]; + + // Truncate if too long + if (firstParagraph.length > maxLength) { + return firstParagraph.substring(0, maxLength).trim() + '...'; + } + + return firstParagraph; } diff --git a/teams.md/scripts/lib/file-collector.ts b/teams.md/scripts/lib/file-collector.ts index 5ece9f4c1..11311fdc6 100644 --- a/teams.md/scripts/lib/file-collector.ts +++ b/teams.md/scripts/lib/file-collector.ts @@ -5,22 +5,22 @@ import { FrontmatterParser } from './frontmatter-parser'; import readFileUtf8Normalized from '../../src/utils/readFileUtf8Normalized'; interface FileInfo { - name: string; - title: string; - path: string; - order: number; + name: string; + title: string; + path: string; + order: number; } interface FolderStructure { - title: string; - order: number; - path: string; - files: FileInfo[]; - children: { [key: string]: FolderStructure }; + title: string; + order: number; + path: string; + files: FileInfo[]; + children: { [key: string]: FolderStructure }; } interface HierarchicalFiles { - language: { [key: string]: FolderStructure }; + language: { [key: string]: FolderStructure }; } /** @@ -30,41 +30,41 @@ interface HierarchicalFiles { * @returns Array of file paths */ export function collectFiles(dirPath: string, extensions: string[] = ['.md', '.mdx']): string[] { - const files: string[] = []; - - if (!fs.existsSync(dirPath)) { - console.warn(`āš ļø Directory not found: ${dirPath}`); - return files; - } - - /** - * Recursively traverse directory - * @param currentPath - Current directory path - */ - function traverse(currentPath: string): void { - const items = fs.readdirSync(currentPath, { withFileTypes: true }); - - for (const item of items) { - const fullPath = path.join(currentPath, item.name); - - if (item.isDirectory()) { - // Skip common directories that don't contain docs - if (!shouldSkipDirectory(item.name)) { - traverse(fullPath); - } - } else if (item.isFile()) { - const ext = path.extname(item.name).toLowerCase(); - if (extensions.includes(ext)) { - files.push(fullPath); - } - } + const files: string[] = []; + + if (!fs.existsSync(dirPath)) { + console.warn(`āš ļø Directory not found: ${dirPath}`); + return files; + } + + /** + * Recursively traverse directory + * @param currentPath - Current directory path + */ + function traverse(currentPath: string): void { + const items = fs.readdirSync(currentPath, { withFileTypes: true }); + + for (const item of items) { + const fullPath = path.join(currentPath, item.name); + + if (item.isDirectory()) { + // Skip common directories that don't contain docs + if (!shouldSkipDirectory(item.name)) { + traverse(fullPath); } + } else if (item.isFile()) { + const ext = path.extname(item.name).toLowerCase(); + if (extensions.includes(ext)) { + files.push(fullPath); + } + } } + } - traverse(dirPath); + traverse(dirPath); - // Sort files for consistent ordering - return files.sort(); + // Sort files for consistent ordering + return files.sort(); } /** @@ -73,18 +73,18 @@ export function collectFiles(dirPath: string, extensions: string[] = ['.md', '.m * @returns True if directory should be skipped */ export function shouldSkipDirectory(dirName: string): boolean { - const skipDirs = [ - 'node_modules', - '.git', - 'build', - 'dist', - '.next', - '.docusaurus', - 'coverage', - '__pycache__' - ]; - - return skipDirs.includes(dirName) || dirName.startsWith('.'); + const skipDirs = [ + 'node_modules', + '.git', + 'build', + 'dist', + '.next', + '.docusaurus', + 'coverage', + '__pycache__', + ]; + + return skipDirs.includes(dirName) || dirName.startsWith('.'); } /** @@ -94,13 +94,13 @@ export function shouldSkipDirectory(dirName: string): boolean { * @returns Hierarchically organized file structure */ export function getHierarchicalFiles(basePath: string, language: string): HierarchicalFiles { - const langPath = path.join(basePath, 'docs', language); + const langPath = path.join(basePath, 'docs', language); - const structure: HierarchicalFiles = { - language: buildHierarchicalStructure(langPath) - }; + const structure: HierarchicalFiles = { + language: buildHierarchicalStructure(langPath), + }; - return structure; + return structure; } /** @@ -109,160 +109,174 @@ export function getHierarchicalFiles(basePath: string, language: string): Hierar * @returns Hierarchical structure with folders and files */ export function buildHierarchicalStructure(rootPath: string): { [key: string]: FolderStructure } { - if (!fs.existsSync(rootPath)) { - return {}; - } + if (!fs.existsSync(rootPath)) { + return {}; + } + + const structure: { [key: string]: FolderStructure } = {}; + const seenTitles = new Map(); // Track titles and their file paths for duplicate detection + + /** + * Recursively processes a directory + * @param dirPath - Current directory path + * @param currentLevel - Current level in the structure + */ + function processDirectory( + dirPath: string, + currentLevel: { files: FileInfo[]; children: { [key: string]: FolderStructure } } + ): void { + const items = fs.readdirSync(dirPath, { withFileTypes: true }); + + // Collect folders and files separately + const folders: FileInfo[] = []; + const files: FileInfo[] = []; + + for (const item of items) { + const fullPath = path.join(dirPath, item.name); + + if (item.isDirectory() && !shouldSkipDirectory(item.name)) { + // Process subdirectory + // Check for both README.md and index.mdx + const readmePath = path.join(fullPath, 'README.md'); + const indexPath = path.join(fullPath, 'index.mdx'); + let folderOrder = 999; + let folderTitle = item.name; + + // Get folder ordering from README.md or index.mdx + const indexFilePath = fs.existsSync(readmePath) + ? readmePath + : fs.existsSync(indexPath) + ? indexPath + : null; + + if (indexFilePath) { + try { + const indexContent = readFileUtf8Normalized(indexFilePath); + const { frontmatter, content } = FrontmatterParser.extract(indexContent); + + // Skip this entire folder if index file is marked to ignore + if (frontmatter.llms === 'ignore' || frontmatter.llms === false) { + continue; // Skip this folder entirely + } - const structure: { [key: string]: FolderStructure } = {}; - const seenTitles = new Map(); // Track titles and their file paths for duplicate detection - - /** - * Recursively processes a directory - * @param dirPath - Current directory path - * @param currentLevel - Current level in the structure - */ - function processDirectory(dirPath: string, currentLevel: { files: FileInfo[]; children: { [key: string]: FolderStructure } }): void { - const items = fs.readdirSync(dirPath, { withFileTypes: true }); - - // Collect folders and files separately - const folders: FileInfo[] = []; - const files: FileInfo[] = []; - - for (const item of items) { - const fullPath = path.join(dirPath, item.name); - - if (item.isDirectory() && !shouldSkipDirectory(item.name)) { - // Process subdirectory - // Check for both README.md and index.mdx - const readmePath = path.join(fullPath, 'README.md'); - const indexPath = path.join(fullPath, 'index.mdx'); - let folderOrder = 999; - let folderTitle = item.name; - - // Get folder ordering from README.md or index.mdx - const indexFilePath = fs.existsSync(readmePath) ? readmePath : (fs.existsSync(indexPath) ? indexPath : null); - - if (indexFilePath) { - try { - const indexContent = readFileUtf8Normalized(indexFilePath); - const { frontmatter, content } = FrontmatterParser.extract(indexContent); - - // Skip this entire folder if index file is marked to ignore - if (frontmatter.llms === 'ignore' || frontmatter.llms === false) { - continue; // Skip this folder entirely - } - - // If index file is marked ignore-file, skip just the file but process folder - // (folderOrder and folderTitle will use defaults) - - folderOrder = (frontmatter.sidebar_position as number) || 999; - - // Extract title from frontmatter or first # header - if (frontmatter.title || frontmatter.sidebar_label) { - folderTitle = (frontmatter.title || frontmatter.sidebar_label) as string; - } else { - // Extract from first # header - const headerMatch = content.match(/^#\s+(.+)$/m); - if (headerMatch) { - folderTitle = headerMatch[1].trim(); - } - } - } catch (error) { - // Ignore errors reading index file - } - } - - folders.push({ - name: item.name, - title: folderTitle, - path: fullPath, - order: folderOrder - }); - } else if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.mdx'))) { - // Process file - let fileOrder = 999; - let fileTitle = item.name; - - try { - const fileContent = readFileUtf8Normalized(fullPath); - const { frontmatter, content } = FrontmatterParser.extract(fileContent); - - // Skip this file if marked to ignore (including ignore-file) - if (frontmatter.llms === 'ignore' || frontmatter.llms === 'ignore-file' || frontmatter.llms === false) { - continue; // Skip this file - } - - fileOrder = (frontmatter.sidebar_position as number) || 999; - - // Extract title from first # header - const headerMatch = content.match(/^#\s+(.+)$/m); - if (headerMatch) { - fileTitle = headerMatch[1].trim(); - } - - // Check for duplicate titles - if (seenTitles.has(fileTitle)) { - const existingPath = seenTitles.get(fileTitle)!; - throw new Error( - `Duplicate title found: "${fileTitle}"\n` + - ` First occurrence: ${existingPath}\n` + - ` Duplicate found in: ${fullPath}` - ); - } - seenTitles.set(fileTitle, fullPath); - } catch (error) { - // Re-throw to fail the build - throw error; - } - - files.push({ - name: item.name, - title: fileTitle, - path: fullPath, - order: fileOrder - }); + // If index file is marked ignore-file, skip just the file but process folder + // (folderOrder and folderTitle will use defaults) + + folderOrder = (frontmatter.sidebar_position as number) || 999; + + // Extract title from frontmatter or first # header + if (frontmatter.title || frontmatter.sidebar_label) { + folderTitle = (frontmatter.title || frontmatter.sidebar_label) as string; + } else { + // Extract from first # header + const headerMatch = content.match(/^#\s+(.+)$/m); + if (headerMatch) { + folderTitle = headerMatch[1].trim(); + } } + } catch (error) { + // Ignore errors reading index file + } } - // Sort files by order and add to current level - files.sort((a, b) => { - if (a.order !== b.order) return a.order - b.order; - return a.name.localeCompare(b.name); + folders.push({ + name: item.name, + title: folderTitle, + path: fullPath, + order: folderOrder, }); - - if (files.length > 0) { - if (!currentLevel.files) currentLevel.files = []; - currentLevel.files.push(...files); + } else if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.mdx'))) { + // Process file + let fileOrder = 999; + let fileTitle = item.name; + + try { + const fileContent = readFileUtf8Normalized(fullPath); + const { frontmatter, content } = FrontmatterParser.extract(fileContent); + + // Skip this file if marked to ignore (including ignore-file) + if ( + frontmatter.llms === 'ignore' || + frontmatter.llms === 'ignore-file' || + frontmatter.llms === false + ) { + continue; // Skip this file + } + + fileOrder = (frontmatter.sidebar_position as number) || 999; + + // Extract title from first # header + const headerMatch = content.match(/^#\s+(.+)$/m); + if (headerMatch) { + fileTitle = headerMatch[1].trim(); + } + + // Check for duplicate titles + if (seenTitles.has(fileTitle)) { + const existingPath = seenTitles.get(fileTitle)!; + throw new Error( + `Duplicate title found: "${fileTitle}"\n` + + ` First occurrence: ${existingPath}\n` + + ` Duplicate found in: ${fullPath}` + ); + } + seenTitles.set(fileTitle, fullPath); + } catch (error) { + // Re-throw to fail the build + throw error; } - // Sort folders by order and process each one - folders.sort((a, b) => { - if (a.order !== b.order) return a.order - b.order; - return a.name.localeCompare(b.name); + files.push({ + name: item.name, + title: fileTitle, + path: fullPath, + order: fileOrder, }); + } + } - if (!currentLevel.children) currentLevel.children = {}; + // Sort files by order and add to current level + files.sort((a, b) => { + if (a.order !== b.order) return a.order - b.order; + return a.name.localeCompare(b.name); + }); - for (const folder of folders) { - currentLevel.children[folder.name] = { - title: folder.title, - order: folder.order, - path: folder.path, - files: [], - children: {} - }; + if (files.length > 0) { + if (!currentLevel.files) currentLevel.files = []; + currentLevel.files.push(...files); + } - // Recursively process subdirectory - processDirectory(folder.path, currentLevel.children[folder.name]); - } + // Sort folders by order and process each one + folders.sort((a, b) => { + if (a.order !== b.order) return a.order - b.order; + return a.name.localeCompare(b.name); + }); + + if (!currentLevel.children) currentLevel.children = {}; + + for (const folder of folders) { + currentLevel.children[folder.name] = { + title: folder.title, + order: folder.order, + path: folder.path, + files: [], + children: {}, + }; + + // Recursively process subdirectory + processDirectory(folder.path, currentLevel.children[folder.name]); } + } - // Create a temporary wrapper to handle the root properly - const tempWrapper: { files: FileInfo[]; children: { [key: string]: FolderStructure } } = { files: [], children: {} }; - processDirectory(rootPath, tempWrapper); + // Create a temporary wrapper to handle the root properly + const tempWrapper: { files: FileInfo[]; children: { [key: string]: FolderStructure } } = { + files: [], + children: {}, + }; + processDirectory(rootPath, tempWrapper); - // Return the children (which contain the actual folder structure) - return tempWrapper.children; + // Return the children (which contain the actual folder structure) + return tempWrapper.children; } /** @@ -271,27 +285,25 @@ export function buildHierarchicalStructure(rootPath: string): { [key: string]: F * @returns Priority files for small version */ export function getPriorityFiles(organized: any): string[] { - const priorityFiles: string[] = []; + const priorityFiles: string[] = []; - // Add welcome/overview files - priorityFiles.push(...organized.main.welcome); + // Add welcome/overview files + priorityFiles.push(...organized.main.welcome); - // Add key team concepts - const keyTeamFiles = organized.main.teams.filter((file: string) => - file.includes('core-concepts') || - file.includes('README.md') - ); - priorityFiles.push(...keyTeamFiles); + // Add key team concepts + const keyTeamFiles = organized.main.teams.filter( + (file: string) => file.includes('core-concepts') || file.includes('README.md') + ); + priorityFiles.push(...keyTeamFiles); - // Add getting started files - priorityFiles.push(...organized.language.gettingStarted); + // Add getting started files + priorityFiles.push(...organized.language.gettingStarted); - // Add essential README files - const essentialReadmes = organized.language.essentials.filter((file: string) => - file.includes('README.md') || - file.includes('app-basics') - ); - priorityFiles.push(...essentialReadmes); + // Add essential README files + const essentialReadmes = organized.language.essentials.filter( + (file: string) => file.includes('README.md') || file.includes('app-basics') + ); + priorityFiles.push(...essentialReadmes); - return priorityFiles; + return priorityFiles; } diff --git a/teams.md/scripts/lib/frontmatter-parser.ts b/teams.md/scripts/lib/frontmatter-parser.ts index ff8dddb7e..926ee3fff 100644 --- a/teams.md/scripts/lib/frontmatter-parser.ts +++ b/teams.md/scripts/lib/frontmatter-parser.ts @@ -10,166 +10,177 @@ import readFileUtf8Normalized from '../../src/utils/readFileUtf8Normalized'; export const FRONTMATTER_REGEX = /^---\s*\r?\n([\s\S]*?)\r?\n---/; interface FrontmatterData { - [key: string]: string | number | boolean; + [key: string]: string | number | boolean; } interface ExtractResult { - frontmatter: FrontmatterData; - content: string; - hasFrontmatter: boolean; + frontmatter: FrontmatterData; + content: string; + hasFrontmatter: boolean; } /** * Parser for YAML frontmatter in markdown/MDX files */ export class FrontmatterParser { - /** - * Extracts and parses frontmatter from content using js-yaml - * @param content - Raw file content - * @returns Object with parsed frontmatter and content without frontmatter - */ - static extract(content: string): ExtractResult { - const match = content.match(FRONTMATTER_REGEX); - - if (!match) { - return { - frontmatter: {}, - content: content, - hasFrontmatter: false - }; - } - - try { - // Use js-yaml for robust YAML parsing instead of custom parser - const frontmatter = yaml.load(match[1]) as FrontmatterData || {}; - const contentWithoutFrontmatter = content.replace(match[0], '').trimStart(); - - return { - frontmatter, - content: contentWithoutFrontmatter, - hasFrontmatter: true - }; - } catch (error) { - console.warn(`Warning: Error parsing frontmatter with js-yaml, falling back to simple parser:`, error); - // Fallback to simple parser - const frontmatter = this.parseSimple(match[1]); - const contentWithoutFrontmatter = content.replace(match[0], '').trimStart(); - - return { - frontmatter, - content: contentWithoutFrontmatter, - hasFrontmatter: true - }; - } + /** + * Extracts and parses frontmatter from content using js-yaml + * @param content - Raw file content + * @returns Object with parsed frontmatter and content without frontmatter + */ + static extract(content: string): ExtractResult { + const match = content.match(FRONTMATTER_REGEX); + + if (!match) { + return { + frontmatter: {}, + content: content, + hasFrontmatter: false, + }; } - /** - * Parses frontmatter text into an object (simple parser - kept for fallback) - * @param frontmatterText - Raw frontmatter content (without --- delimiters) - * @returns Parsed frontmatter object - */ - static parseSimple(frontmatterText: string): FrontmatterData { - const frontmatter: FrontmatterData = {}; - const lines = frontmatterText.split('\n'); - - for (const line of lines) { - const match = line.match(/^(\w+):\s*(.+)$/); - if (!match) continue; - - const key = match[1]; - let value: string | number | boolean = match[2].trim(); - - value = this._parseValue(value); - frontmatter[key] = value; - } + try { + // Use js-yaml for robust YAML parsing instead of custom parser + const frontmatter = (yaml.load(match[1]) as FrontmatterData) || {}; + const contentWithoutFrontmatter = content.replace(match[0], '').trimStart(); + + return { + frontmatter, + content: contentWithoutFrontmatter, + hasFrontmatter: true, + }; + } catch (error) { + console.warn( + `Warning: Error parsing frontmatter with js-yaml, falling back to simple parser:`, + error + ); + // Fallback to simple parser + const frontmatter = this.parseSimple(match[1]); + const contentWithoutFrontmatter = content.replace(match[0], '').trimStart(); + + return { + frontmatter, + content: contentWithoutFrontmatter, + hasFrontmatter: true, + }; + } + } + + /** + * Parses frontmatter text into an object (simple parser - kept for fallback) + * @param frontmatterText - Raw frontmatter content (without --- delimiters) + * @returns Parsed frontmatter object + */ + static parseSimple(frontmatterText: string): FrontmatterData { + const frontmatter: FrontmatterData = {}; + const lines = frontmatterText.split('\n'); + + for (const line of lines) { + const match = line.match(/^(\w+):\s*(.+)$/); + if (!match) continue; + + const key = match[1]; + let value: string | number | boolean = match[2].trim(); + + value = this._parseValue(value); + frontmatter[key] = value; + } - return frontmatter; + return frontmatter; + } + + /** + * Extracts frontmatter from a file + * @param filePath - Path to the file + * @returns Parsed frontmatter or null if file doesn't exist + */ + static extractFromFile(filePath: string): ExtractResult | null { + if (!fs.existsSync(filePath)) { + return null; } - /** - * Extracts frontmatter from a file - * @param filePath - Path to the file - * @returns Parsed frontmatter or null if file doesn't exist - */ - static extractFromFile(filePath: string): ExtractResult | null { - if (!fs.existsSync(filePath)) { - return null; - } - - try { - const content = readFileUtf8Normalized(filePath); - return this.extract(content); - } catch (error) { - console.warn(`āš ļø Error reading frontmatter from ${filePath}:`, (error as Error).message); - return null; - } + try { + const content = readFileUtf8Normalized(filePath); + return this.extract(content); + } catch (error) { + console.warn(`āš ļø Error reading frontmatter from ${filePath}:`, (error as Error).message); + return null; + } + } + + /** + * Gets a specific property from frontmatter + * @param content - File content or path + * @param propertyName - Property to extract + * @param defaultValue - Default value if property not found + * @returns Property value or default + */ + static getProperty( + content: string, + propertyName: string, + defaultValue?: T + ): T | undefined { + const result = + typeof content === 'string' && fs.existsSync(content) + ? this.extractFromFile(content) + : this.extract(content); + + if (!result) { + return defaultValue; } - /** - * Gets a specific property from frontmatter - * @param content - File content or path - * @param propertyName - Property to extract - * @param defaultValue - Default value if property not found - * @returns Property value or default - */ - static getProperty(content: string, propertyName: string, defaultValue?: T): T | undefined { - const result = typeof content === 'string' && fs.existsSync(content) - ? this.extractFromFile(content) - : this.extract(content); - - if (!result) { - return defaultValue; - } - - const { frontmatter } = result; - - return frontmatter[propertyName] !== undefined - ? (frontmatter[propertyName] as T) - : defaultValue; + const { frontmatter } = result; + + return frontmatter[propertyName] !== undefined + ? (frontmatter[propertyName] as T) + : defaultValue; + } + + /** + * Checks if a file or content should be ignored based on frontmatter + * @param content - File content or path + * @returns True if should be ignored + */ + static shouldIgnore(content: string): boolean { + const result = + typeof content === 'string' && fs.existsSync(content) + ? this.extractFromFile(content) + : this.extract(content); + + if (!result) { + return false; } - /** - * Checks if a file or content should be ignored based on frontmatter - * @param content - File content or path - * @returns True if should be ignored - */ - static shouldIgnore(content: string): boolean { - const result = typeof content === 'string' && fs.existsSync(content) - ? this.extractFromFile(content) - : this.extract(content); - - if (!result) { - return false; - } - - const { frontmatter } = result; - const llmsValue = frontmatter.llms; - return llmsValue === 'ignore' || llmsValue === 'ignore-file' || llmsValue === false; + const { frontmatter } = result; + const llmsValue = frontmatter.llms; + return llmsValue === 'ignore' || llmsValue === 'ignore-file' || llmsValue === false; + } + + /** + * Parses a value from frontmatter, converting types as needed + * @private + * @param value - Raw value string + * @returns Parsed value (string, number, boolean) + */ + private static _parseValue(value: string): string | number | boolean { + // Remove surrounding quotes + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + return value.slice(1, -1); } - /** - * Parses a value from frontmatter, converting types as needed - * @private - * @param value - Raw value string - * @returns Parsed value (string, number, boolean) - */ - private static _parseValue(value: string): string | number | boolean { - // Remove surrounding quotes - if ((value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'"))) { - return value.slice(1, -1); - } - - // Parse booleans - if (value === 'true') return true; - if (value === 'false') return false; - - // Parse integers - if (/^\d+$/.test(value)) { - return parseInt(value, 10); - } - - // Return as string - return value; + // Parse booleans + if (value === 'true') return true; + if (value === 'false') return false; + + // Parse integers + if (/^\d+$/.test(value)) { + return parseInt(value, 10); } + + // Return as string + return value; + } } diff --git a/teams.md/sidebars.ts b/teams.md/sidebars.ts index be0203d6b..c4244ef67 100644 --- a/teams.md/sidebars.ts +++ b/teams.md/sidebars.ts @@ -1,4 +1,4 @@ -import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; +import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; export default { default: [{ type: 'autogenerated', dirName: '.' }], diff --git a/teams.md/src/components/FileCodeBlock.tsx b/teams.md/src/components/FileCodeBlock.tsx index 713298d7b..5d312f9a8 100644 --- a/teams.md/src/components/FileCodeBlock.tsx +++ b/teams.md/src/components/FileCodeBlock.tsx @@ -1,37 +1,37 @@ -import { useEffect, useState, PropsWithChildren } from "react"; -import CodeBlock from "@theme/CodeBlock"; -import useBaseUrl from "@docusaurus/useBaseUrl"; +import { useEffect, useState, PropsWithChildren } from 'react'; +import CodeBlock from '@theme/CodeBlock'; +import useBaseUrl from '@docusaurus/useBaseUrl'; export type FileCodeBlockParams = { - readonly src: string; - readonly lang?: string; + readonly src: string; + readonly lang?: string; }; export default function FileCodeBlock({ src, lang }: PropsWithChildren) { - const [code, setCode] = useState(); - const url = useBaseUrl(src); + const [code, setCode] = useState(); + const url = useBaseUrl(src); - useEffect(() => { - (async () => { - try { - const res = await fetch(url); + useEffect(() => { + (async () => { + try { + const res = await fetch(url); - if (!res.ok || res.status != 200) { - throw new Error(`failed to load file code block with status "${res.status}"`); - } + if (!res.ok || res.status != 200) { + throw new Error(`failed to load file code block with status "${res.status}"`); + } - const blob = await res.blob(); - const data = await blob.text(); - setCode(data.trim()); - } catch (err) { - console.error('failed to load file code block', err); - } - })(); - }, [src]); + const blob = await res.blob(); + const data = await blob.text(); + setCode(data.trim()); + } catch (err) { + console.error('failed to load file code block', err); + } + })(); + }, [src]); - return ( - - {code} - - ); + return ( + + {code} + + ); } diff --git a/teams.md/src/components/include/essentials/api/csharp.incl.md b/teams.md/src/components/include/essentials/api/csharp.incl.md index 26a70d511..a93cb02ee 100644 --- a/teams.md/src/components/include/essentials/api/csharp.incl.md +++ b/teams.md/src/components/include/essentials/api/csharp.incl.md @@ -4,11 +4,11 @@ -| Area | Description | -| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Area | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user). Also includes `Reactions` for adding/removing emoji reactions to messages | -| `Meetings` | Gives your application access to meeting details and participant information via `GetByIdAsync` and `GetParticipantAsync` | -| `Teams` | Gives your application access to team or channel details | +| `Meetings` | Gives your application access to meeting details and participant information via `GetByIdAsync` and `GetParticipantAsync` | +| `Teams` | Gives your application access to team or channel details | @@ -19,7 +19,6 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - ```csharp app.OnMessage(async context => { @@ -27,7 +26,6 @@ app.OnMessage(async context => }); ``` - ```csharp diff --git a/teams.md/src/components/include/essentials/api/python.incl.md b/teams.md/src/components/include/essentials/api/python.incl.md index cc0c25457..8021ea0f4 100644 --- a/teams.md/src/components/include/essentials/api/python.incl.md +++ b/teams.md/src/components/include/essentials/api/python.incl.md @@ -4,11 +4,11 @@ -| Area | Description | -| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Area | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user). Also includes `reactions` for adding/removing emoji reactions to messages | -| `meetings` | Gives your application access to meeting details and participant information via `get_by_id` and `get_participant` | -| `teams` | Gives your application access to team or channel details | +| `meetings` | Gives your application access to meeting details and participant information via `get_by_id` and `get_participant` | +| `teams` | Gives your application access to team or channel details | diff --git a/teams.md/src/components/include/essentials/api/typescript.incl.md b/teams.md/src/components/include/essentials/api/typescript.incl.md index c38dd70d1..5173ff84c 100644 --- a/teams.md/src/components/include/essentials/api/typescript.incl.md +++ b/teams.md/src/components/include/essentials/api/typescript.incl.md @@ -4,11 +4,11 @@ -| Area | Description | -| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Area | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user). Also includes `reactions` for adding/removing emoji reactions to messages | -| `meetings` | Gives your application access to meeting details and participant information via `getById` and `getParticipant` | -| `teams` | Gives your application access to team or channel details | +| `meetings` | Gives your application access to meeting details and participant information via `getById` and `getParticipant` | +| `teams` | Gives your application access to team or channel details | diff --git a/teams.md/src/components/include/essentials/app-authentication/csharp.incl.md b/teams.md/src/components/include/essentials/app-authentication/csharp.incl.md index 3c50dc100..689c1f076 100644 --- a/teams.md/src/components/include/essentials/app-authentication/csharp.incl.md +++ b/teams.md/src/components/include/essentials/app-authentication/csharp.incl.md @@ -5,11 +5,14 @@ The environment file approach is not yet supported for C#. You need to configure ::: In your `Program.cs`, replace the initialization: + ```csharp var builder = WebApplication.CreateBuilder(args); builder.AddTeams(); ``` + with the following code to enable User Assigned Managed Identity authentication: + ```csharp var builder = WebApplication.CreateBuilder(args); diff --git a/teams.md/src/components/include/essentials/graph/csharp.incl.md b/teams.md/src/components/include/essentials/graph/csharp.incl.md index 282323470..2f7cfb181 100644 --- a/teams.md/src/components/include/essentials/graph/csharp.incl.md +++ b/teams.md/src/components/include/essentials/graph/csharp.incl.md @@ -39,7 +39,6 @@ To access the graph using the user's token, you need to do this as part of a mes import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - ```csharp app.OnMessage(async context => { @@ -51,7 +50,6 @@ app.OnMessage(async context => }); ``` - `userGraph` diff --git a/teams.md/src/components/include/essentials/on-activity/csharp.incl.md b/teams.md/src/components/include/essentials/on-activity/csharp.incl.md index c8ad8866a..de878e11f 100644 --- a/teams.md/src/components/include/essentials/on-activity/csharp.incl.md +++ b/teams.md/src/components/include/essentials/on-activity/csharp.incl.md @@ -20,14 +20,15 @@ In the above example, the `context.activity` parameter is of type `MessageActivi The `OnActivity` activity handlers (and attributes) follow a [middleware](https://www.patterns.dev/vanilla/mediator-pattern/) pattern similar to how `dotnet` middlewares work. This means that for each activity handler, a `Next` function is passed in which can be called to pass control to the next handler. This allows you to build a chain of handlers that can process the same activity in different ways. - ```csharp - app.OnMessage(async context => - { - Console.WriteLine("global logger"); - context.Next(); // pass control onward - return Task.CompletedTask; - }); - ``` + +```csharp +app.OnMessage(async context => +{ + Console.WriteLine("global logger"); + context.Next(); // pass control onward + return Task.CompletedTask; +}); +``` ```csharp app.OnMessage(async context => @@ -40,14 +41,13 @@ app.OnMessage(async context => // Conditionally pass control to the next handler context.Next(); }); - + app.OnMessage(async context => { // Fallthrough to the final handler await context.Send($"Hello! you said {context.Activity.Text}"); }); - ``` - +``` diff --git a/teams.md/src/components/include/essentials/sending-messages/csharp.incl.md b/teams.md/src/components/include/essentials/sending-messages/csharp.incl.md index 8c817f0f8..05f8ecb5a 100644 --- a/teams.md/src/components/include/essentials/sending-messages/csharp.incl.md +++ b/teams.md/src/components/include/essentials/sending-messages/csharp.incl.md @@ -9,12 +9,12 @@ app.OnMessage(async context => - ```csharp - app.OnVerifyState(async context => - { - await context.Send("You have successfully signed in!"); - }); - ``` +```csharp +app.OnVerifyState(async context => +{ + await context.Send("You have successfully signed in!"); +}); +``` diff --git a/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/csharp.incl.md b/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/csharp.incl.md index 70087bdb2..b2b09a469 100644 --- a/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/csharp.incl.md +++ b/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/csharp.incl.md @@ -50,4 +50,4 @@ public static async Task SendTargetedNotification(string conversationId, Account .WithRecipient(recipient, isTargeted: true) ); } -``` \ No newline at end of file +``` diff --git a/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/python.incl.md b/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/python.incl.md index 3078b055a..0b4297062 100644 --- a/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/python.incl.md +++ b/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/python.incl.md @@ -48,4 +48,4 @@ async def send_targeted_notification(conversation_id: str, recipient: Account): MessageActivityInput(text="This is a private notification just for you!") .with_recipient(recipient, is_targeted=True) ) -``` \ No newline at end of file +``` diff --git a/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/typescript.incl.md b/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/typescript.incl.md index 6bfff73a1..ce702de26 100644 --- a/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/typescript.incl.md +++ b/teams.md/src/components/include/essentials/sending-messages/proactive-messaging/typescript.incl.md @@ -35,7 +35,7 @@ const sendProactiveNotification = async (userId: string) => { if (!conversationId) { return; } - const activity = new MessageActivity('Hey! It\'s been a while. How are you?'); + const activity = new MessageActivity("Hey! It's been a while. How are you?"); await app.send(conversationId, activity); }; ``` @@ -49,8 +49,10 @@ import { MessageActivity, Account } from '@microsoft/teams.api'; const sendTargetedNotification = async (conversationId: string, recipient: Account) => { await app.send( conversationId, - new MessageActivity('This is a private notification just for you!') - .withRecipient(recipient, true) + new MessageActivity('This is a private notification just for you!').withRecipient( + recipient, + true + ) ); }; -``` \ No newline at end of file +``` diff --git a/teams.md/src/components/include/essentials/sending-messages/typescript.incl.md b/teams.md/src/components/include/essentials/sending-messages/typescript.incl.md index d6abc899b..228908d06 100644 --- a/teams.md/src/components/include/essentials/sending-messages/typescript.incl.md +++ b/teams.md/src/components/include/essentials/sending-messages/typescript.incl.md @@ -54,8 +54,7 @@ import { MessageActivity } from '@microsoft/teams.api'; app.on('message', async ({ send, activity }) => { // Using withRecipient with isTargeted=true explicitly targets the specified recipient await send( - new MessageActivity('This message is only visible to you!') - .withRecipient(activity.from, true) + new MessageActivity('This message is only visible to you!').withRecipient(activity.from, true) ); }); ``` diff --git a/teams.md/src/components/include/getting-started/code-basics/csharp.incl.md b/teams.md/src/components/include/getting-started/code-basics/csharp.incl.md index 514f91e22..2fe476d88 100644 --- a/teams.md/src/components/include/getting-started/code-basics/csharp.incl.md +++ b/teams.md/src/components/include/getting-started/code-basics/csharp.incl.md @@ -49,7 +49,6 @@ teams.OnMessage(async context => }); ``` - Listens for all incoming messages using `onMessage` handler. diff --git a/teams.md/src/components/include/getting-started/running-in-teams/csharp.incl.md b/teams.md/src/components/include/getting-started/running-in-teams/csharp.incl.md index 4abc4d8ba..8d695efa2 100644 --- a/teams.md/src/components/include/getting-started/running-in-teams/csharp.incl.md +++ b/teams.md/src/components/include/getting-started/running-in-teams/csharp.incl.md @@ -6,4 +6,4 @@ [INFO] Echo.Microsoft.Teams.Plugins.AspNetCore.DevTools Available at http://localhost:3979/devtools [INFO] Microsoft.Hosting.Lifetime Application started. Press Ctrl+C to shut down. [INFO] Microsoft.Hosting.Lifetime Hosting environment: Development -``` \ No newline at end of file +``` diff --git a/teams.md/src/components/include/getting-started/running-in-teams/python.incl.md b/teams.md/src/components/include/getting-started/running-in-teams/python.incl.md index 621203542..f05605880 100644 --- a/teams.md/src/components/include/getting-started/running-in-teams/python.incl.md +++ b/teams.md/src/components/include/getting-started/running-in-teams/python.incl.md @@ -9,4 +9,4 @@ INFO: Waiting for application startup. [INFO] @teams/app Teams app started successfully INFO: Application startup complete.. INFO: Uvicorn running on http://0.0.0.0:3979 (Press CTRL+C to quit) -``` \ No newline at end of file +``` diff --git a/teams.md/src/components/include/getting-started/running-in-teams/typescript.incl.md b/teams.md/src/components/include/getting-started/running-in-teams/typescript.incl.md index dfeddde4a..ae8f74345 100644 --- a/teams.md/src/components/include/getting-started/running-in-teams/typescript.incl.md +++ b/teams.md/src/components/include/getting-started/running-in-teams/typescript.incl.md @@ -9,4 +9,4 @@ [WARN] @teams/app/devtools āš ļø Devtools are not secure and should not be used production environments āš ļø [INFO] @teams/app/http listening on port 3978 šŸš€ [INFO] @teams/app/devtools available at http://localhost:3979/devtools -``` \ No newline at end of file +``` diff --git a/teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/python.incl.md b/teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/python.incl.md index 7c4839511..9adab5ec6 100644 --- a/teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/python.incl.md +++ b/teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/python.incl.md @@ -162,4 +162,4 @@ async def handle_card_action(ctx: ActivityContext[AdaptiveCardInvokeActivity]) - :::note The `data` values are accessible as a dictionary and can be accessed using `.get()` method for safe access. -::: \ No newline at end of file +::: diff --git a/teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-client/python.incl.md b/teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-client/python.incl.md index 2668803b2..52d5b756e 100644 --- a/teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-client/python.incl.md +++ b/teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-client/python.incl.md @@ -141,7 +141,6 @@ advanced_plugin.on_use_plugin( advanced_prompt = ChatPrompt(model=completions_model, plugins=[advanced_plugin]) ``` - ```mermaid diff --git a/teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-server/typescript.incl.md b/teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-server/typescript.incl.md index cb9b295de..a99ccfe73 100644 --- a/teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-server/typescript.incl.md +++ b/teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-server/typescript.incl.md @@ -59,4 +59,3 @@ app.event('a2a:message', async ({ respond, requestContext }) => { await respond(result); }); ``` - diff --git a/teams.md/src/components/include/in-depth-guides/ai/function-calling/csharp.incl.md b/teams.md/src/components/include/in-depth-guides/ai/function-calling/csharp.incl.md index 0c3a1e86e..b987f2383 100644 --- a/teams.md/src/components/include/in-depth-guides/ai/function-calling/csharp.incl.md +++ b/teams.md/src/components/include/in-depth-guides/ai/function-calling/csharp.incl.md @@ -379,4 +379,5 @@ The LLM can call functions sequentially - using the output of one function as in ::: + N/A diff --git a/teams.md/src/components/include/in-depth-guides/ai/function-calling/python.incl.md b/teams.md/src/components/include/in-depth-guides/ai/function-calling/python.incl.md index 188f1be59..5fff31923 100644 --- a/teams.md/src/components/include/in-depth-guides/ai/function-calling/python.incl.md +++ b/teams.md/src/components/include/in-depth-guides/ai/function-calling/python.incl.md @@ -162,4 +162,5 @@ async def handle_multiple_functions(ctx: ActivityContext[MessageActivity]): ``` + N/A diff --git a/teams.md/src/components/include/in-depth-guides/ai/setup-and-prereqs/typescript.incl.md b/teams.md/src/components/include/in-depth-guides/ai/setup-and-prereqs/typescript.incl.md index deeafd709..dfd6241ef 100644 --- a/teams.md/src/components/include/in-depth-guides/ai/setup-and-prereqs/typescript.incl.md +++ b/teams.md/src/components/include/in-depth-guides/ai/setup-and-prereqs/typescript.incl.md @@ -66,15 +66,16 @@ const model = new OpenAIChatModel({ const model = new OpenAIChatModel({ apiKey: 'your-api-key', model: 'gpt-4o', - endpoint: 'your-endpoint', // Azure only + endpoint: 'your-endpoint', // Azure only apiVersion: 'your-api-version', // Azure only - baseUrl: 'your-base-url', // Custom base URL - organization: 'your-org-id', // Optional - project: 'your-project-id', // Optional + baseUrl: 'your-base-url', // Custom base URL + organization: 'your-org-id', // Optional + project: 'your-project-id', // Optional }); ``` **Environment variables automatically loaded:** + - `OPENAI_API_KEY` or `AZURE_OPENAI_API_KEY` - `AZURE_OPENAI_ENDPOINT` (Azure only) - `OPENAI_API_VERSION` (Azure only) diff --git a/teams.md/src/components/include/in-depth-guides/meeting-events/typescript.incl.md b/teams.md/src/components/include/in-depth-guides/meeting-events/typescript.incl.md index 57e57483e..510430b9b 100644 --- a/teams.md/src/components/include/in-depth-guides/meeting-events/typescript.incl.md +++ b/teams.md/src/components/include/in-depth-guides/meeting-events/typescript.incl.md @@ -13,11 +13,9 @@ app.on('meetingStart', async ({ activity, send }) => { const card = new AdaptiveCard( new TextBlock(`'${meetingData.Title}' has started at ${startTime}.`, { wrap: true, - weight: 'Bolder' + weight: 'Bolder', }), - new ActionSet( - new OpenUrlAction(meetingData.JoinUrl).withTitle('Join the meeting') - ) + new ActionSet(new OpenUrlAction(meetingData.JoinUrl).withTitle('Join the meeting')) ); await send(card); @@ -39,7 +37,7 @@ app.on('meetingEnd', async ({ activity, send }) => { const card = new AdaptiveCard( new TextBlock(`'${meetingData.Title}' has ended at ${endTime}.`, { wrap: true, - weight: 'Bolder' + weight: 'Bolder', }) ); @@ -63,7 +61,7 @@ app.on('meetingParticipantJoin', async ({ activity, send }) => { const card = new AdaptiveCard( new TextBlock(`${member} has joined the meeting as ${role}.`, { wrap: true, - weight: 'Bolder' + weight: 'Bolder', }) ); @@ -86,7 +84,7 @@ app.on('meetingParticipantLeave', async ({ activity, send }) => { const card = new AdaptiveCard( new TextBlock(`${member} has left the meeting.`, { wrap: true, - weight: 'Bolder' + weight: 'Bolder', }) ); diff --git a/teams.md/src/components/include/in-depth-guides/message-reactions/csharp.incl.md b/teams.md/src/components/include/in-depth-guides/message-reactions/csharp.incl.md index 647df0a84..cc89719f8 100644 --- a/teams.md/src/components/include/in-depth-guides/message-reactions/csharp.incl.md +++ b/teams.md/src/components/include/in-depth-guides/message-reactions/csharp.incl.md @@ -4,7 +4,7 @@ app.OnMessage(async context => { await context.Send("Hello! I'll react to this message."); - + // Add a reaction to the incoming message await context.Api.Conversations.Reactions.AddAsync( context.Activity.Conversation.Id, @@ -25,7 +25,7 @@ app.OnMessage(async context => context.Activity.Id, ReactionType.Heart ); - + // Wait a bit, then remove it await Task.Delay(2000); await context.Api.Conversations.Reactions.DeleteAsync( diff --git a/teams.md/src/components/include/in-depth-guides/message-reactions/python.incl.md b/teams.md/src/components/include/in-depth-guides/message-reactions/python.incl.md index 8bd33d0a8..e3cc55569 100644 --- a/teams.md/src/components/include/in-depth-guides/message-reactions/python.incl.md +++ b/teams.md/src/components/include/in-depth-guides/message-reactions/python.incl.md @@ -4,7 +4,7 @@ @app.on_message async def handle_message(ctx: ActivityContext[MessageActivity]): await ctx.send("Hello! I'll react to this message.") - + # Add a reaction to the incoming message await ctx.api.conversations.reactions.add( ctx.activity.conversation.id, @@ -26,7 +26,7 @@ async def handle_message(ctx: ActivityContext[MessageActivity]): ctx.activity.id, 'heart' ) - + # Wait a bit, then remove it await asyncio.sleep(2) await ctx.api.conversations.reactions.delete( diff --git a/teams.md/src/components/include/in-depth-guides/message-reactions/typescript.incl.md b/teams.md/src/components/include/in-depth-guides/message-reactions/typescript.incl.md index f145a29d0..d78725951 100644 --- a/teams.md/src/components/include/in-depth-guides/message-reactions/typescript.incl.md +++ b/teams.md/src/components/include/in-depth-guides/message-reactions/typescript.incl.md @@ -3,13 +3,9 @@ ```typescript app.on('message', async ({ activity, api, send }) => { await send("Hello! I'll react to this message."); - + // Add a reaction to the incoming message - await api.conversations.reactions.add( - activity.conversation.id, - activity.id, - 'like' - ); + await api.conversations.reactions.add(activity.conversation.id, activity.id, 'like'); }); ``` @@ -18,19 +14,11 @@ app.on('message', async ({ activity, api, send }) => { ```typescript app.on('message', async ({ activity, api }) => { // First, add a reaction - await api.conversations.reactions.add( - activity.conversation.id, - activity.id, - 'heart' - ); - + await api.conversations.reactions.add(activity.conversation.id, activity.id, 'heart'); + // Wait a bit, then remove it - await new Promise(resolve => setTimeout(resolve, 2000)); - await api.conversations.reactions.delete( - activity.conversation.id, - activity.id, - 'heart' - ); + await new Promise((resolve) => setTimeout(resolve, 2000)); + await api.conversations.reactions.delete(activity.conversation.id, activity.id, 'heart'); }); ``` @@ -69,9 +57,5 @@ For advanced scenarios, you can access the underlying HTTP client or create a cu const { api } = context; // Use reactions API -await api.conversations.reactions.add( - conversationId, - activityId, - 'like' -); +await api.conversations.reactions.add(conversationId, activityId, 'like'); ``` diff --git a/teams.md/src/components/include/in-depth-guides/user-authentication/csharp.incl.md b/teams.md/src/components/include/in-depth-guides/user-authentication/csharp.incl.md index 1222be1cf..b9d31584b 100644 --- a/teams.md/src/components/include/in-depth-guides/user-authentication/csharp.incl.md +++ b/teams.md/src/components/include/in-depth-guides/user-authentication/csharp.incl.md @@ -88,6 +88,7 @@ teams.OnMessage("/signout", async context => await context.Send("you have been signed out!"); }); ``` + -N/A \ No newline at end of file +N/A diff --git a/teams.md/src/components/include/in-depth-guides/user-authentication/python.incl.md b/teams.md/src/components/include/in-depth-guides/user-authentication/python.incl.md index c1fecee3a..95f1956a1 100644 --- a/teams.md/src/components/include/in-depth-guides/user-authentication/python.incl.md +++ b/teams.md/src/components/include/in-depth-guides/user-authentication/python.incl.md @@ -87,17 +87,19 @@ async def handle_signout_message(ctx: ActivityContext[MessageActivity]): ``` + import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; ## Regional Configs + You may be building a regional bot that is deployed in a specific Azure region (such as West Europe, East US, etc.) rather than global. This is important for organizations that have data residency requirements or want to reduce latency by keeping data and authentication flows within a specific area. These examples use West Europe, but follow the equivalent for other regions. -To configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. +To configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. 1. Deploy a new App Registration in `westeurope`. 2. Deploy and link a new Enterprise Application (Service Principal) on Microsoft Entra in `westeurope`. @@ -107,8 +109,8 @@ To configure a new regional bot in Azure, you must setup your resoures in the de ![Authentication Tab](/screenshots/regional-auth.png) 5. In your `.env` file (or wherever you set your environment variables), add your `OAUTH_URL`. For example: -`OAUTH_URL=https://europe.token.botframework.com` - + `OAUTH_URL=https://europe.token.botframework.com` + To configure a new regional bot with ATK, you will need to make a few updates. Note that this assumes you have not yet deployed the bot previously. @@ -117,6 +119,6 @@ To configure a new regional bot with ATK, you will need to make a few updates. N 2. In `manifest.json`, in `validDomains`, `*.botframework.com` should be replaced by `europe.token.botframework.com` 3. In `aad.manifest.json`, replace `https://token.botframework.com/.auth/web/redirect` with `https://europe.token.botframework.com/.auth/web/redirect` 4. In your `.env` file, add your `OAUTH_URL`. For example: -`OAUTH_URL=https://europe.token.botframework.com`. - - \ No newline at end of file + `OAUTH_URL=https://europe.token.botframework.com`. + + diff --git a/teams.md/src/components/include/in-depth-guides/user-authentication/typescript.incl.md b/teams.md/src/components/include/in-depth-guides/user-authentication/typescript.incl.md index 2df8cf15f..cd4b95e13 100644 --- a/teams.md/src/components/include/in-depth-guides/user-authentication/typescript.incl.md +++ b/teams.md/src/components/include/in-depth-guides/user-authentication/typescript.incl.md @@ -51,7 +51,7 @@ app.event('signin', async ({ send, token }) => { import * as endpoints from '@microsoft/teams.graph-endpoints'; app.message('/whoami', async ({ send, userGraph, signin }) => { - if (!await signin()) { + if (!(await signin())) { return; } const me = await userGraph.call(endpoints.me.get); @@ -82,17 +82,19 @@ app.message('/signout', async ({ send, signout, isSignedIn }) => { ``` + import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; ## Regional Configs + You may be building a regional bot that is deployed in a specific Azure region (such as West Europe, East US, etc.) rather than global. This is important for organizations that have data residency requirements or want to reduce latency by keeping data and authentication flows within a specific area. These examples use West Europe, but follow the equivalent for other regions. -To configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. +To configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. 1. Deploy a new App Registration in `westeurope`. 2. Deploy and link a new Enterprise Application (Service Principal) on Microsoft Entra in `westeurope`. @@ -102,8 +104,8 @@ To configure a new regional bot in Azure, you must setup your resoures in the de ![Authentication Tab](/screenshots/regional-auth.png) 5. In your `.env` file (or wherever you set your environment variables), add your `OAUTH_URL`. For example: -`OAUTH_URL=https://europe.token.botframework.com` - + `OAUTH_URL=https://europe.token.botframework.com` + To configure a new regional bot with ATK, you will need to make a few updates. Note that this assumes you have not yet deployed the bot previously. @@ -112,6 +114,6 @@ To configure a new regional bot with ATK, you will need to make a few updates. N 2. In `manifest.json`, in `validDomains`, `*.botframework.com` should be replaced by `europe.token.botframework.com` 3. In `aad.manifest.json`, replace `https://token.botframework.com/.auth/web/redirect` with `https://europe.token.botframework.com/.auth/web/redirect` 4. In your `.env` file, add your `OAUTH_URL`. For example: -`OAUTH_URL=https://europe.token.botframework.com` - - \ No newline at end of file + `OAUTH_URL=https://europe.token.botframework.com` + + diff --git a/teams.md/src/components/include/migrations/botbuilder/integration/csharp.incl.md b/teams.md/src/components/include/migrations/botbuilder/integration/csharp.incl.md index 302ab1c03..7deaae9ff 100644 --- a/teams.md/src/components/include/migrations/botbuilder/integration/csharp.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/integration/csharp.incl.md @@ -80,4 +80,4 @@ ``` - \ No newline at end of file + diff --git a/teams.md/src/components/include/migrations/botbuilder/integration/python.incl.md b/teams.md/src/components/include/migrations/botbuilder/integration/python.incl.md index 272d59b9b..861264af5 100644 --- a/teams.md/src/components/include/migrations/botbuilder/integration/python.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/integration/python.incl.md @@ -67,4 +67,4 @@ ``` - \ No newline at end of file + diff --git a/teams.md/src/components/include/migrations/botbuilder/proactive-activities/csharp.incl.md b/teams.md/src/components/include/migrations/botbuilder/proactive-activities/csharp.incl.md index e3752983b..084b5fcee 100644 --- a/teams.md/src/components/include/migrations/botbuilder/proactive-activities/csharp.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/proactive-activities/csharp.incl.md @@ -12,16 +12,17 @@ + using Microsoft.Teams.Apps; // highlight-error-start -- var conversationReference = new ConversationReference -- { + +- var conversationReference = new ConversationReference +- { - ServiceUrl = "...", - Bot = new ChannelAccount { ... }, - ChannelId = "msteams", - Conversation = new ConversationAccount { ... }, - User = new ChannelAccount { ... } -- }; +- }; - -- await adapter.ContinueConversationAsync( +- await adapter.ContinueConversationAsync( - configuration["MicrosoftAppId"], - conversationReference, - async (turnContext, cancellationToken) => @@ -29,48 +30,50 @@ - await turnContext.SendActivityAsync("proactive hello", cancellationToken: cancellationToken); - }, - default); - // highlight-error-end - // highlight-success-start -+ var teams = app.UseTeams(); -+ await teams.Send("your-conversation-id", "proactive hello"); - // highlight-success-end - ``` - + // highlight-error-end + // highlight-success-start + +* var teams = app.UseTeams(); +* await teams.Send("your-conversation-id", "proactive hello"); + // highlight-success-end + ` - ```csharp showLineNumbers - using Microsoft.Bot.Builder; - using Microsoft.Bot.Builder.Integration.AspNet.Core; - using Microsoft.Bot.Schema; + `csharp showLineNumbers + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Integration.AspNet.Core; + using Microsoft.Bot.Schema; + + // highlight-start + var conversationReference = new ConversationReference + { + ServiceUrl = "...", + Bot = new ChannelAccount { ... }, + ChannelId = "msteams", + Conversation = new ConversationAccount { ... }, + User = new ChannelAccount { ... } + }; + + await adapter.ContinueConversationAsync( + configuration["MicrosoftAppId"], + conversationReference, + async (turnContext, cancellationToken) => + { + await turnContext.SendActivityAsync("proactive hello", cancellationToken: cancellationToken); + }, + default); + // highlight-end + ``` - // highlight-start - var conversationReference = new ConversationReference - { - ServiceUrl = "...", - Bot = new ChannelAccount { ... }, - ChannelId = "msteams", - Conversation = new ConversationAccount { ... }, - User = new ChannelAccount { ... } - }; + + + ```csharp showLineNumbers + using Microsoft.Teams.Apps; - await adapter.ContinueConversationAsync( - configuration["MicrosoftAppId"], - conversationReference, - async (turnContext, cancellationToken) => - { - await turnContext.SendActivityAsync("proactive hello", cancellationToken: cancellationToken); - }, - default); - // highlight-end - ``` - - - ```csharp showLineNumbers - using Microsoft.Teams.Apps; + // highlight-start + var teams = app.UseTeams(); + await teams.Send("your-conversation-id", "proactive hello"); + // highlight-end + ``` - // highlight-start - var teams = app.UseTeams(); - await teams.Send("your-conversation-id", "proactive hello"); - // highlight-end - ``` - - \ No newline at end of file + + diff --git a/teams.md/src/components/include/migrations/botbuilder/proactive-activities/python.incl.md b/teams.md/src/components/include/migrations/botbuilder/proactive-activities/python.incl.md index 94a2e3e5e..5724ed051 100644 --- a/teams.md/src/components/include/migrations/botbuilder/proactive-activities/python.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/proactive-activities/python.incl.md @@ -12,69 +12,73 @@ + from microsoft_teams.apps import App # highlight-error-start -- adapter = CloudAdapter(ConfigurationBotFrameworkAuthentication(config)) - # highlight-error-end - # highlight-success-line -+ app = App() - # highlight-error-start -- conversation_reference = ConversationReference( +- adapter = CloudAdapter(ConfigurationBotFrameworkAuthentication(config)) + # highlight-error-end + # highlight-success-line + +* app = App() + + # highlight-error-start + +- conversation_reference = ConversationReference( - service_url="...", - bot=ChannelAccount(...), - channel_id="msteams", - conversation=ConversationAccount(...), - user=ChannelAccount(...) -- ) +- ) - -- async def send_proactive(turn_context: TurnContext): +- async def send_proactive(turn_context: TurnContext): - await turn_context.send_activity("proactive hello") - -- await adapter.continue_conversation( +- await adapter.continue_conversation( - conversation_reference, - send_proactive, -- ) - # highlight-error-end - # highlight-success-start -+ await app.send("your-conversation-id", "proactive hello") - # highlight-success-end - ``` - +- ) + # highlight-error-end + # highlight-success-start + +* await app.send("your-conversation-id", "proactive hello") # highlight-success-end + ` - ```python showLineNumbers - from botbuilder.core import TurnContext - from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication - from botbuilder.schema import ChannelAccount, ConversationAccount, ConversationReference + `python showLineNumbers + from botbuilder.core import TurnContext + from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication + from botbuilder.schema import ChannelAccount, ConversationAccount, ConversationReference + + adapter = CloudAdapter(ConfigurationBotFrameworkAuthentication(config)) + + # highlight-start + conversation_reference = ConversationReference( + service_url="...", + bot=ChannelAccount(...), + channel_id="msteams", + conversation=ConversationAccount(...), + user=ChannelAccount(...) + ) - adapter = CloudAdapter(ConfigurationBotFrameworkAuthentication(config)) + async def send_proactive(turn_context: TurnContext): + await turn_context.send_activity("proactive hello") - # highlight-start - conversation_reference = ConversationReference( - service_url="...", - bot=ChannelAccount(...), - channel_id="msteams", - conversation=ConversationAccount(...), - user=ChannelAccount(...) - ) + await adapter.continue_conversation( + conversation_reference, + send_proactive + ) + # highlight-end + ``` - async def send_proactive(turn_context: TurnContext): - await turn_context.send_activity("proactive hello") + + + ```python showLineNumbers + from microsoft_teams.apps import App - await adapter.continue_conversation( - conversation_reference, - send_proactive - ) - # highlight-end - ``` - - - ```python showLineNumbers - from microsoft_teams.apps import App + app = App() - app = App() + # highlight-start + await app.send("your-conversation-id", "proactive hello") + # highlight-end + ``` - # highlight-start - await app.send("your-conversation-id", "proactive hello") - # highlight-end - ``` - - \ No newline at end of file + + diff --git a/teams.md/src/components/include/migrations/botbuilder/proactive-activities/typescript.incl.md b/teams.md/src/components/include/migrations/botbuilder/proactive-activities/typescript.incl.md index df978fa60..e1f8253fb 100644 --- a/teams.md/src/components/include/migrations/botbuilder/proactive-activities/typescript.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/proactive-activities/typescript.incl.md @@ -14,14 +14,18 @@ + import { App } from '@microsoft/teams.apps'; // highlight-error-start -- const auth = new ConfigurationBotFrameworkAuthentication(process.env); -- const adapter = new CloudAdapter(auth); - // highlight-error-end - // highlight-success-line -+ const app = new App(); + +- const auth = new ConfigurationBotFrameworkAuthentication(process.env); +- const adapter = new CloudAdapter(auth); + // highlight-error-end + // highlight-success-line + +* const app = new App(); + (async () => { // highlight-error-start + - const conversationReference: ConversationReference = { - serviceUrl: '...', - bot: { ... }, @@ -35,52 +39,56 @@ - }); // highlight-error-end // highlight-success-start -+ await app.start(); -+ await app.send('your-conversation-id', 'proactive hello'); - // highlight-success-end - }()); - ``` - - - ```typescript showLineNumbers - import { - CloudAdapter, - ConfigurationBotFrameworkAuthentication, - ConversationReference, - } from 'botbuilder'; - - const auth = new ConfigurationBotFrameworkAuthentication(process.env); - const adapter = new CloudAdapter(auth); - - // highlight-start - (async () => { - const conversationReference: ConversationReference = { - serviceUrl: '...', - bot: { ... }, - channelId: 'msteams', - conversation: { ... }, - user: { ... }, - }; - - await adapter.continueConversationAsync(process.env.MicrosoftAppId ?? '', conversationReference, async context => { - await context.sendActivity('proactive hello'); - }); - }()); - // highlight-end - ``` - - - ```typescript showLineNumbers - import { App } from '@microsoft/teams.apps'; - - const app = new App(); - - // highlight-start - (async () => { - await app.start(); - await app.send('your-conversation-id', 'proactive hello'); - }()); - // highlight-end - ``` - - \ No newline at end of file + +* await app.start(); +* await app.send('your-conversation-id', 'proactive hello'); + // highlight-success-end + }()); + ``` + + + + ```typescript showLineNumbers + import { + CloudAdapter, + ConfigurationBotFrameworkAuthentication, + ConversationReference, + } from 'botbuilder'; + + const auth = new ConfigurationBotFrameworkAuthentication(process.env); + const adapter = new CloudAdapter(auth); + + // highlight-start + (async () => { + const conversationReference: ConversationReference = { + serviceUrl: '...', + bot: { ... }, + channelId: 'msteams', + conversation: { ... }, + user: { ... }, + }; + + await adapter.continueConversationAsync(process.env.MicrosoftAppId ?? '', conversationReference, async context => { + await context.sendActivity('proactive hello'); + }); + }()); + // highlight-end + ``` + + + + ```typescript showLineNumbers + import { App } from '@microsoft/teams.apps'; + + const app = new App(); + + // highlight-start + (async () => { + await app.start(); + await app.send('your-conversation-id', 'proactive hello'); + }()); + // highlight-end + ``` + + + diff --git a/teams.md/src/components/include/migrations/botbuilder/sending-activities/csharp.incl.md b/teams.md/src/components/include/migrations/botbuilder/sending-activities/csharp.incl.md index ddb2aad34..9a40ebe16 100644 --- a/teams.md/src/components/include/migrations/botbuilder/sending-activities/csharp.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/sending-activities/csharp.incl.md @@ -14,61 +14,64 @@ //highlight-success-end // highlight-error-start -- public class MyActivityHandler : ActivityHandler -- { + +- public class MyActivityHandler : ActivityHandler +- { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - await turnContext.SendActivityAsync( -- Activity.CreateTypingActivity(), +- Activity.CreateTypingActivity(), - cancellationToken: cancellationToken); - } -- } - // highlight-error-end - // highlight-success-start -+ var teams = app.UseTeams(); -+ teams.OnMessage(async (context) => -+ { -+ await context.Send(new Activity(type:"typing")); -+ }); - // highlight-success-end - ``` - +- } + // highlight-error-end + // highlight-success-start + +* var teams = app.UseTeams(); +* teams.OnMessage(async (context) => +* { +* await context.Send(new Activity(type:"typing")); +* }); + // highlight-success-end + ` - ```csharp showLineNumbers - using Microsoft.Bot.Builder; - using Microsoft.Bot.Schema; - - public class MyActivityHandler : ActivityHandler - { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - // highlight-next-line - await turnContext.SendActivityAsync( - Activity.CreateTypingActivity(), - cancellationToken: cancellationToken); - } - } - ``` - - - ```csharp showLineNumbers - using Microsoft.Teams.Apps; - using Microsoft.Teams.Plugins.AspNetCore.Extensions; - using Microsoft.Teams.Api.Activities; - - var teams = app.UseTeams(); - teams.OnMessage(async (context) => - { - // highlight-next-line - await context.Send(new Activity(type:"typing")); - }); - ``` - - + `csharp showLineNumbers + using Microsoft.Bot.Builder; + using Microsoft.Bot.Schema; + + public class MyActivityHandler : ActivityHandler + { + protected override async Task OnMessageActivityAsync( + ITurnContext turnContext, + CancellationToken cancellationToken) + { + // highlight-next-line + await turnContext.SendActivityAsync( + Activity.CreateTypingActivity(), + cancellationToken: cancellationToken); + } + } + ``` + + + + ```csharp showLineNumbers + using Microsoft.Teams.Apps; + using Microsoft.Teams.Plugins.AspNetCore.Extensions; + using Microsoft.Teams.Api.Activities; + + var teams = app.UseTeams(); + teams.OnMessage(async (context) => + { + // highlight-next-line + await context.Send(new Activity(type:"typing")); + }); + ``` + + + ## Strings @@ -85,56 +88,59 @@ //highlight-success-end // highlight-error-start -- public class MyActivityHandler : ActivityHandler -- { + +- public class MyActivityHandler : ActivityHandler +- { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - await turnContext.SendActivityAsync("hello world", cancellationToken: cancellationToken); - } -- } - // highlight-error-end - // highlight-success-start -+ var teams = app.UseTeams(); -+ teams.OnMessage(async (context) => -+ { -+ await context.Send("hello world"); -+ }); - // highlight-success-end - ``` - +- } + // highlight-error-end + // highlight-success-start + +* var teams = app.UseTeams(); +* teams.OnMessage(async (context) => +* { +* await context.Send("hello world"); +* }); + // highlight-success-end + ` - ```csharp showLineNumbers - using Microsoft.Bot.Builder; - using Microsoft.Bot.Schema; - - public class MyActivityHandler : ActivityHandler - { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - // highlight-next-line - await turnContext.SendActivityAsync("hello world", cancellationToken: cancellationToken); - } - } - ``` - - - ```csharp showLineNumbers - using Microsoft.Teams.Apps; - using Microsoft.Teams.Plugins.AspNetCore.Extensions; - - var teams = app.UseTeams(); - teams.OnMessage(async (context) => - { - // highlight-next-line - await context.Send("hello world"); - }); - ``` - - + `csharp showLineNumbers + using Microsoft.Bot.Builder; + using Microsoft.Bot.Schema; + + public class MyActivityHandler : ActivityHandler + { + protected override async Task OnMessageActivityAsync( + ITurnContext turnContext, + CancellationToken cancellationToken) + { + // highlight-next-line + await turnContext.SendActivityAsync("hello world", cancellationToken: cancellationToken); + } + } + ``` + + + + ```csharp showLineNumbers + using Microsoft.Teams.Apps; + using Microsoft.Teams.Plugins.AspNetCore.Extensions; + + var teams = app.UseTeams(); + teams.OnMessage(async (context) => + { + // highlight-next-line + await context.Send("hello world"); + }); + ``` + + + ## Adaptive Cards @@ -152,8 +158,9 @@ // highlight-success-end // highlight-error-start -- public class MyActivityHandler : ActivityHandler -- { + +- public class MyActivityHandler : ActivityHandler +- { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) @@ -175,65 +182,67 @@ - var activity = MessageFactory.Attachment(attachment); - await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken); - } -- } - // highlight-error-end - // highlight-success-start -+ var teams = app.UseTeams(); -+ teams.OnMessage(async (context) => -+ { -+ await context.Send(new AdaptiveCard(new TextBlock("hello world"))); -+ }); - // highlight-success-end - ``` - +- } + // highlight-error-end + // highlight-success-start + +* var teams = app.UseTeams(); +* teams.OnMessage(async (context) => +* { +* await context.Send(new AdaptiveCard(new TextBlock("hello world"))); +* }); + // highlight-success-end + ` - ```csharp showLineNumbers - using Microsoft.Bot.Builder; - using Microsoft.Bot.Schema; - - public class MyActivityHandler : ActivityHandler - { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - // highlight-start - var card = new - { - type = "AdaptiveCard", - version = "1.0", - body = new[] - { - new { type = "TextBlock", text = "hello world" } - } - }; - var attachment = new Attachment - { - ContentType = "application/vnd.microsoft.card.adaptive", - Content = card - }; - var activity = MessageFactory.Attachment(attachment); - await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken); - // highlight-end - } - } - ``` - - - ```csharp showLineNumbers - using Microsoft.Teams.Cards; - using Microsoft.Teams.Apps; - using Microsoft.Teams.Plugins.AspNetCore.Extensions; - - var teams = app.UseTeams(); - teams.OnMessage(async (context) => - { - // highlight-next-line - await context.Send(new AdaptiveCard(new TextBlock("hello world"))); - }); - ``` - - + `csharp showLineNumbers + using Microsoft.Bot.Builder; + using Microsoft.Bot.Schema; + + public class MyActivityHandler : ActivityHandler + { + protected override async Task OnMessageActivityAsync( + ITurnContext turnContext, + CancellationToken cancellationToken) + { + // highlight-start + var card = new + { + type = "AdaptiveCard", + version = "1.0", + body = new[] + { + new { type = "TextBlock", text = "hello world" } + } + }; + var attachment = new Attachment + { + ContentType = "application/vnd.microsoft.card.adaptive", + Content = card + }; + var activity = MessageFactory.Attachment(attachment); + await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken); + // highlight-end + } + } + ``` + + + + ```csharp showLineNumbers + using Microsoft.Teams.Cards; + using Microsoft.Teams.Apps; + using Microsoft.Teams.Plugins.AspNetCore.Extensions; + + var teams = app.UseTeams(); + teams.OnMessage(async (context) => + { + // highlight-next-line + await context.Send(new AdaptiveCard(new TextBlock("hello world"))); + }); + ``` + + + ## Attachments @@ -251,8 +260,9 @@ // highlight-success-end // highlight-error-start -- public class MyActivityHandler : ActivityHandler -- { + +- public class MyActivityHandler : ActivityHandler +- { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) @@ -260,53 +270,54 @@ - var activity = MessageFactory.Attachment(new Attachment { /* ... */ }); - await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken); - } -- } - // highlight-error-end - // highlight-success-start -+ var teams = app.UseTeams(); -+ teams.OnMessage(async (context) => -+ { -+ var activity = new MessageActivity(); -+ activity.AddAttachment(new Attachment { /* ... */ }); -+ await context.SendAsync(activity); -+ }); - // highlight-success-end - ``` - +- } + // highlight-error-end + // highlight-success-start + +* var teams = app.UseTeams(); +* teams.OnMessage(async (context) => +* { +* var activity = new MessageActivity(); +* activity.AddAttachment(new Attachment { /* ... */ }); +* await context.SendAsync(activity); +* }); + // highlight-success-end + ` - ```csharp showLineNumbers - using Microsoft.Bot.Builder; - using Microsoft.Bot.Schema; - - public class MyActivityHandler : ActivityHandler - { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - // highlight-start - var activity = MessageFactory.Attachment(new Attachment { /* ... */ }); - await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken); - // highlight-end - } - } - ``` - - - ```csharp showLineNumbers - using Microsoft.Teams.Api; - using Microsoft.Teams.Apps; - using Microsoft.Teams.Plugins.AspNetCore.Extensions; - - var teams = app.UseTeams(); - teams.OnMessage(async (context) => - { - // highlight-start - var activity = new MessageActivity(); - activity.AddAttachment(new Attachment { /* ... */ }); - await context.SendAsync(activity); - // highlight-end - }); - ``` - - + `csharp showLineNumbers + using Microsoft.Bot.Builder; + using Microsoft.Bot.Schema; + + public class MyActivityHandler : ActivityHandler + { + protected override async Task OnMessageActivityAsync( + ITurnContext turnContext, + CancellationToken cancellationToken) + { + // highlight-start + var activity = MessageFactory.Attachment(new Attachment { /* ... */ }); + await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken); + // highlight-end + } + } + ``` + + + + ```csharp showLineNumbers + using Microsoft.Teams.Api; + using Microsoft.Teams.Apps; + using Microsoft.Teams.Plugins.AspNetCore.Extensions; + + var teams = app.UseTeams(); + teams.OnMessage(async (context) => + { + // highlight-start + var activity = new MessageActivity(); + activity.AddAttachment(new Attachment { /* ... */ }); + await context.SendAsync(activity); + // highlight-end + }); + ``` + + diff --git a/teams.md/src/components/include/migrations/botbuilder/sending-activities/python.incl.md b/teams.md/src/components/include/migrations/botbuilder/sending-activities/python.incl.md index f9ccb4107..079f01428 100644 --- a/teams.md/src/components/include/migrations/botbuilder/sending-activities/python.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/sending-activities/python.incl.md @@ -13,40 +13,45 @@ # highlight-success-end # highlight-error-start -- class MyActivityHandler(ActivityHandler): + +- class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - await turn_context.send_activity(Activity(type="typing")) - # highlight-error-end - # highlight-success-start -+ @app.on_message -+ async def on_message(context: ActivityContext[MessageActivity]): -+ await context.send(TypingActivityInput()) - # highlight-success-end - ``` - - - ```python showLineNumbers - from botbuilder.core import ActivityHandler, TurnContext - from botbuilder.schema import Activity - - class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - # highlight-next-line - await turn_context.send_activity(Activity(type="typing")) - ``` - - - ```python showLineNumbers - from microsoft_teams.api import MessageActivity, TypingActivityInput - from microsoft_teams.apps import ActivityContext, App - - @app.on_message - async def on_message(context: ActivityContext[MessageActivity]): - # highlight-next-line - await context.send(TypingActivityInput()) - ``` - - + # highlight-error-end + # highlight-success-start + +* @app.on_message +* async def on_message(context: ActivityContext[MessageActivity]): +* await context.send(TypingActivityInput()) + # highlight-success-end + ``` + + + + ```python showLineNumbers + from botbuilder.core import ActivityHandler, TurnContext + from botbuilder.schema import Activity + + class MyActivityHandler(ActivityHandler): + async def on_message_activity(self, turn_context: TurnContext): + # highlight-next-line + await turn_context.send_activity(Activity(type="typing")) + ``` + + + + ```python showLineNumbers + from microsoft_teams.api import MessageActivity, TypingActivityInput + from microsoft_teams.apps import ActivityContext, App + + @app.on_message + async def on_message(context: ActivityContext[MessageActivity]): + # highlight-next-line + await context.send(TypingActivityInput()) + ``` + + + ## Strings @@ -62,39 +67,44 @@ # highlight-success-end # highlight-error-start -- class MyActivityHandler(ActivityHandler): + +- class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - await turn_context.send_activity("hello world") - # highlight-error-end - # highlight-success-start -+ @app.on_message -+ async def on_message(context: ActivityContext[MessageActivity]): -+ await context.send("hello world") - # highlight-success-end - ``` - - - ```python showLineNumbers - from botbuilder.core import ActivityHandler, TurnContext - - class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - # highlight-next-line - await turn_context.send_activity("hello world") - ``` - - - ```python showLineNumbers - from microsoft_teams.api import MessageActivity - from microsoft_teams.apps import ActivityContext, App - - @app.on_message - async def on_message(context: ActivityContext[MessageActivity]): - # highlight-next-line - await context.send("hello world") - ``` - - + # highlight-error-end + # highlight-success-start + +* @app.on_message +* async def on_message(context: ActivityContext[MessageActivity]): +* await context.send("hello world") + # highlight-success-end + ``` + + + + ```python showLineNumbers + from botbuilder.core import ActivityHandler, TurnContext + + class MyActivityHandler(ActivityHandler): + async def on_message_activity(self, turn_context: TurnContext): + # highlight-next-line + await turn_context.send_activity("hello world") + ``` + + + + ```python showLineNumbers + from microsoft_teams.api import MessageActivity + from microsoft_teams.apps import ActivityContext, App + + @app.on_message + async def on_message(context: ActivityContext[MessageActivity]): + # highlight-next-line + await context.send("hello world") + ``` + + + ## Adaptive Cards @@ -112,48 +122,53 @@ # highlight-success-end # highlight-error-start -- class MyActivityHandler(ActivityHandler): + +- class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - card = {"type": "AdaptiveCard", "version": "1.0", "body": [{"type": "TextBlock", "text": "hello world"}]} - attachment = Attachment(content_type="application/vnd.microsoft.card.adaptive", content=card) - activity = Activity(type="message", attachments=[attachment]) - await turn_context.send_activity(activity) - # highlight-error-end - # highlight-success-start -+ @app.on_message -+ async def on_message(context: ActivityContext[MessageActivity]): -+ await context.send(AdaptiveCard().with_body([TextBlock(text="Hello from Adaptive Card!")])) - # highlight-success-end - ``` - - - ```python showLineNumbers - from botbuilder.core import ActivityHandler, TurnContext - from botbuilder.schema import Activity, Attachment - - class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - # hightlight-start - card = {"type": "AdaptiveCard", "version": "1.0", "body": [{"type": "TextBlock", "text": "hello world"}]} - attachment = Attachment(content_type="application/vnd.microsoft.card.adaptive", content=card) - activity = Activity(type="message", attachments=[attachment]) - await turn_context.send_activity(activity) - # highlight-end - ``` - - - ```python showLineNumbers - from microsoft_teams.api import MessageActivity - from microsoft_teams.apps import ActivityContext, App - from microsoft_teams.cards import AdaptiveCard, TextBlock - - @app.on_message - async def on_message(context: ActivityContext[MessageActivity]): - # highlight-next-line - await context.send(AdaptiveCard(body=[TextBlock(text="Hello from Adaptive Card!")])) - ``` - - + # highlight-error-end + # highlight-success-start + +* @app.on_message +* async def on_message(context: ActivityContext[MessageActivity]): +* await context.send(AdaptiveCard().with_body([TextBlock(text="Hello from Adaptive Card!")])) + # highlight-success-end + ``` + + + + ```python showLineNumbers + from botbuilder.core import ActivityHandler, TurnContext + from botbuilder.schema import Activity, Attachment + + class MyActivityHandler(ActivityHandler): + async def on_message_activity(self, turn_context: TurnContext): + # hightlight-start + card = {"type": "AdaptiveCard", "version": "1.0", "body": [{"type": "TextBlock", "text": "hello world"}]} + attachment = Attachment(content_type="application/vnd.microsoft.card.adaptive", content=card) + activity = Activity(type="message", attachments=[attachment]) + await turn_context.send_activity(activity) + # highlight-end + ``` + + + + ```python showLineNumbers + from microsoft_teams.api import MessageActivity + from microsoft_teams.apps import ActivityContext, App + from microsoft_teams.cards import AdaptiveCard, TextBlock + + @app.on_message + async def on_message(context: ActivityContext[MessageActivity]): + # highlight-next-line + await context.send(AdaptiveCard(body=[TextBlock(text="Hello from Adaptive Card!")])) + ``` + + + ## Attachments @@ -170,47 +185,52 @@ # highlight-success-end # highlight-error-start -- class MyActivityHandler(ActivityHandler): + +- class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - attachment = Attachment(...) - activity = Activity(type="message", attachments=[attachment]) - await turn_context.send_activity(activity) - # highlight-error-end - # highlight-success-start -+ @app.on_message -+ async def on_message(context: ActivityContext[MessageActivity]): -+ attachment = Attachment(...) -+ activity = MessageActivityInput().add_attachments([attachment]) -+ await context.send(activity) - # highlight-success-end - ``` - - - ```python showLineNumbers - from botbuilder.core import ActivityHandler, TurnContext - from botbuilder.schema import Activity, Attachment - - class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - # highlight-start - attachment = Attachment(...) - activity = Activity(type="message", attachments=[attachment]) - await turn_context.send_activity(activity) - # highlight-end - ``` - - - ```python showLineNumbers - from microsoft_teams.api import Attachment, MessageActivity, MessageActivityInput - from microsoft_teams.apps import ActivityContext, App - - @app.on_message - async def on_message(context: ActivityContext[MessageActivity]): - # highlight-start - attachment = Attachment(...) - activity = MessageActivityInput().add_attachments([attachment]) - await context.send(activity) - # highlight-end - ``` - - + # highlight-error-end + # highlight-success-start + +* @app.on_message +* async def on_message(context: ActivityContext[MessageActivity]): +* attachment = Attachment(...) +* activity = MessageActivityInput().add_attachments([attachment]) +* await context.send(activity) + # highlight-success-end + ``` + + + + ```python showLineNumbers + from botbuilder.core import ActivityHandler, TurnContext + from botbuilder.schema import Activity, Attachment + + class MyActivityHandler(ActivityHandler): + async def on_message_activity(self, turn_context: TurnContext): + # highlight-start + attachment = Attachment(...) + activity = Activity(type="message", attachments=[attachment]) + await turn_context.send_activity(activity) + # highlight-end + ``` + + + + ```python showLineNumbers + from microsoft_teams.api import Attachment, MessageActivity, MessageActivityInput + from microsoft_teams.apps import ActivityContext, App + + @app.on_message + async def on_message(context: ActivityContext[MessageActivity]): + # highlight-start + attachment = Attachment(...) + activity = MessageActivityInput().add_attachments([attachment]) + await context.send(activity) + # highlight-end + ``` + + + diff --git a/teams.md/src/components/include/migrations/botbuilder/sending-activities/typescript.incl.md b/teams.md/src/components/include/migrations/botbuilder/sending-activities/typescript.incl.md index 1d0e72b10..1bccaa45e 100644 --- a/teams.md/src/components/include/migrations/botbuilder/sending-activities/typescript.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/sending-activities/typescript.incl.md @@ -6,21 +6,25 @@ // highlight-error-start - import { TeamsActivityHandler } from 'botbuilder'; -- export class ActivityHandler extends TeamsActivityHandler { +- export class ActivityHandler extends TeamsActivityHandler { - constructor() { - super(); - this.onMessage(async (context) => { - await context.sendActivity({ type: 'typing' }); - }); - } -- } - // highlight-error-end - // highlight-success-start -+ app.on('message', async ({ send }) => { -+ await send({ type: 'typing' }); -+ }); - // highlight-success-end +- } + // highlight-error-end + // highlight-success-start + +* app.on('message', async ({ send }) => { +* await send({ type: 'typing' }); +* }); + // highlight-success-end + + ``` + ```typescript showLineNumbers @@ -36,6 +40,7 @@ } } ``` + ```typescript showLineNumbers @@ -55,21 +60,25 @@ // highlight-error-start - import { TeamsActivityHandler } from 'botbuilder'; -- export class ActivityHandler extends TeamsActivityHandler { +- export class ActivityHandler extends TeamsActivityHandler { - constructor() { - super(); - this.onMessage(async (context) => { - await context.sendActivity('hello world'); - }); - } -- } - // highlight-error-end - // highlight-success-start -+ app.on('message', async ({ send }) => { -+ await send('hello world'); -+ }); - // highlight-success-end +- } + // highlight-error-end + // highlight-success-start + +* app.on('message', async ({ send }) => { +* await send('hello world'); +* }); + // highlight-success-end + + ``` + ```typescript showLineNumbers @@ -85,6 +94,7 @@ } } ``` + ```typescript showLineNumbers @@ -107,7 +117,8 @@ + import { AdaptiveCard, TextBlock } from '@microsoft/teams.cards'; // highlight-error-start -- export class ActivityHandler extends TeamsActivityHandler { + +- export class ActivityHandler extends TeamsActivityHandler { - constructor() { - super(); - this.onMessage(async (context) => { @@ -127,14 +138,18 @@ - }); - }); - } -- } - // highlight-error-end - // highlight-success-start -+ app.on('message', async ({ send }) => { -+ await send(new AdaptiveCard(new TextBlock('hello world'))); -+ }); - // highlight-success-end +- } + // highlight-error-end + // highlight-success-start + +* app.on('message', async ({ send }) => { +* await send(new AdaptiveCard(new TextBlock('hello world'))); +* }); + // highlight-success-end + + ``` + ```typescript showLineNumbers @@ -164,6 +179,7 @@ } } ``` + ```typescript showLineNumbers @@ -174,6 +190,7 @@ await send(new AdaptiveCard(new TextBlock('hello world'))); }); ``` + @@ -188,7 +205,8 @@ + import { AdaptiveCard, TextBlock } from '@microsoft/teams.cards'; // highlight-error-start -- export class ActivityHandler extends TeamsActivityHandler { + +- export class ActivityHandler extends TeamsActivityHandler { - constructor() { - super(); - this.onMessage(async (context) => { @@ -200,14 +218,18 @@ - }); - }); - } -- } - // highlight-error-end - // highlight-success-start -+ app.on('message', async ({ send }) => { -+ await send(new MessageActivity().addAttachment(...)); -+ }); - // highlight-success-end +- } + // highlight-error-end + // highlight-success-start + +* app.on('message', async ({ send }) => { +* await send(new MessageActivity().addAttachment(...)); +* }); + // highlight-success-end + + ``` + ```typescript showLineNumbers @@ -229,6 +251,7 @@ } } ``` + ```typescript showLineNumbers @@ -238,5 +261,6 @@ await send(new MessageActivity().addAttachment(...)); }); ``` + - \ No newline at end of file + diff --git a/teams.md/src/components/include/migrations/botbuilder/the-api-client/csharp.incl.md b/teams.md/src/components/include/migrations/botbuilder/the-api-client/csharp.incl.md index ff9882939..19b773f93 100644 --- a/teams.md/src/components/include/migrations/botbuilder/the-api-client/csharp.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/the-api-client/csharp.incl.md @@ -10,54 +10,57 @@ // highlight-success-line + using Microsoft.Teams.Apps; - // highlight-error-start -- public class MyActivityHandler : ActivityHandler -- { +// highlight-error-start + +- public class MyActivityHandler : ActivityHandler +- { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken); - } -- } +- } // highlight-error-end // highlight-success-start -+ var teams = app.UseTeams(); -+ teams.OnMessage(async (context) => -+ { -+ var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id); -+ }); + +* var teams = app.UseTeams(); +* teams.OnMessage(async (context) => +* { +* var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id); +* }); // highlight-success-end + +```` + + + ```csharp showLineNumbers + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Teams; + + public class MyActivityHandler : TeamsActivityHandler + { + protected override async Task OnMessageActivityAsync( + ITurnContext turnContext, + CancellationToken cancellationToken) + { + // highlight-next-line + var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken); + } + } ``` - - - ```csharp showLineNumbers - using Microsoft.Bot.Builder; - using Microsoft.Bot.Builder.Teams; + + + ```csharp showLineNumbers + using Microsoft.Teams.Apps; - public class MyActivityHandler : TeamsActivityHandler - { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - // highlight-next-line - var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken); - } - } - ``` - - - ```csharp showLineNumbers - using Microsoft.Teams.Apps; - - app.OnMessage(async (context) => - { - // highlight-next-line - var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id); - }); - ``` - + app.OnMessage(async (context) => + { + // highlight-next-line + var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id); + }); + ``` + @@ -68,3 +71,4 @@ | `TeamsInfo.GetTeamDetailsAsync(context, teamId)` | `Api.Teams.GetByIdAsync(teamId)` | | `TeamsInfo.GetMeetingInfoAsync(context, meetingId)` | `Api.Meetings.GetByIdAsync(meetingId)` | | `TeamsInfo.SendMessageToTeamsChannelAsync(context, teamId, message)` | `Api.Conversations.CreateAsync(CreateRequest)` then `Api.Conversations.Activities.CreateAsync(conversationId, activity)` | +```` diff --git a/teams.md/src/components/include/migrations/botbuilder/the-api-client/python.incl.md b/teams.md/src/components/include/migrations/botbuilder/the-api-client/python.incl.md index cd76e3a13..8a920f177 100644 --- a/teams.md/src/components/include/migrations/botbuilder/the-api-client/python.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/the-api-client/python.incl.md @@ -11,17 +11,21 @@ + from microsoft_teams.apps import ActivityContext + from microsoft_teams.api import MessageActivity - # highlight-error-start -- class MyActivityHandler(ActivityHandler): +# highlight-error-start + +- class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - members = await TeamsInfo.get_members(turn_context) # highlight-error-end # highlight-success-start -+ @app.on_message -+ async def on_message(context: ActivityContext[MessageActivity]): -+ members = await context.api.conversations.members(context.activity.conversation.id).get_all() + +* @app.on_message +* async def on_message(context: ActivityContext[MessageActivity]): +* members = await context.api.conversations.members(context.activity.conversation.id).get_all() + # highlight-success-end - ``` + + ```` ```python showLineNumbers @@ -45,13 +49,14 @@ members = await context.api.conversations.members(context.activity.conversation.id).get() ``` - + + ```` -| BotBuilder (TeamsInfo) | Teams SDK (ApiClient) | -|------------------------|----------------------| -| `TeamsInfo.getMembers(context, user_id)` | `api.conversations.members(conversation_id).get(user_id)` | -| `TeamsInfo.get_team_details(context, team_id)` | `api.teams.get_by_id(team_id)` | -| `TeamsInfo.get_meeting_info(context, meeting_id)` | `api.meetings.get_by_id(meeting_id)` | +| BotBuilder (TeamsInfo) | Teams SDK (ApiClient) | +| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `TeamsInfo.getMembers(context, user_id)` | `api.conversations.members(conversation_id).get(user_id)` | +| `TeamsInfo.get_team_details(context, team_id)` | `api.teams.get_by_id(team_id)` | +| `TeamsInfo.get_meeting_info(context, meeting_id)` | `api.meetings.get_by_id(meeting_id)` | | `TeamsInfo.send_message_to_teams_channel(context, team_id, message)` | `api.conversations.create(CreateConversationParams)` then `api.conversations.activities(conversation_id).create(activity)` | diff --git a/teams.md/src/components/include/migrations/botbuilder/the-api-client/typescript.incl.md b/teams.md/src/components/include/migrations/botbuilder/the-api-client/typescript.incl.md index f1ea7ce86..f1cf9f1a9 100644 --- a/teams.md/src/components/include/migrations/botbuilder/the-api-client/typescript.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/the-api-client/typescript.incl.md @@ -13,64 +13,69 @@ // highlight-success-line + import { App } from '@microsoft/teams.apps'; - // highlight-error-start -- const auth = new ConfigurationBotFrameworkAuthentication(process.env); -- const adapter = new CloudAdapter(auth); +// highlight-error-start + +- const auth = new ConfigurationBotFrameworkAuthentication(process.env); +- const adapter = new CloudAdapter(auth); // highlight-error-end // highlight-success-line -+ const app = new App(); - // highlight-error-start -- export class ActivityHandler extends TeamsActivityHandler { -- constructor() { +* const app = new App(); + +// highlight-error-start + +- export class ActivityHandler extends TeamsActivityHandler { +- constructor() { - super(); - this.onMessage(async (context) => { - const members = await TeamsInfo.getMembers(context); - }); -- } -- } +- } +- } // highlight-error-end // highlight-success-start -+ app.on('message', async ({ api, activity }) => { -+ const members = await api.conversations.members(activity.conversation.id).get(); -+ }); + +* app.on('message', async ({ api, activity }) => { +* const members = await api.conversations.members(activity.conversation.id).get(); +* }); // highlight-success-end - ``` - - - ```typescript showLineNumbers - import { - CloudAdapter, - ConfigurationBotFrameworkAuthentication, - TeamsInfo, - } from 'botbuilder'; - const auth = new ConfigurationBotFrameworkAuthentication(process.env); - const adapter = new CloudAdapter(auth); +```` + + + ```typescript showLineNumbers + import { + CloudAdapter, + ConfigurationBotFrameworkAuthentication, + TeamsInfo, + } from 'botbuilder'; + + const auth = new ConfigurationBotFrameworkAuthentication(process.env); + const adapter = new CloudAdapter(auth); - export class ActivityHandler extends TeamsActivityHandler { - constructor() { - super(); - this.onMessage(async (context) => { - // highlight-next-line - const members = await TeamsInfo.getMembers(context); - }); - } + export class ActivityHandler extends TeamsActivityHandler { + constructor() { + super(); + this.onMessage(async (context) => { + // highlight-next-line + const members = await TeamsInfo.getMembers(context); + }); } - ``` - - - ```typescript showLineNumbers - import { App } from '@microsoft/teams.apps'; + } + ``` + + + ```typescript showLineNumbers + import { App } from '@microsoft/teams.apps'; - const app = new App(); + const app = new App(); - app.on('message', async ({ api, activity }) => { - // highlight-next-line - const members = await api.conversations.members(activity.conversation.id).get(); - }); - ``` - + app.on('message', async ({ api, activity }) => { + // highlight-next-line + const members = await api.conversations.members(activity.conversation.id).get(); + }); + ``` + @@ -81,3 +86,4 @@ | `TeamsInfo.getTeamDetails(context, teamId)` | `api.teams.getById(teamId)` | | `TeamsInfo.getMeetingInfo(context, meetingId)` | `api.meetings.getById(meetingId)` | | `TeamsInfo.sendMessageToTeamsChannel(context, teamId, message)` | `api.conversations.create(CreateConversationParams)` then `api.conversations.activities(conversationId).create(activity)` | +```` diff --git a/teams.md/src/components/include/migrations/botbuilder/user-authentication/typescript.incl.md b/teams.md/src/components/include/migrations/botbuilder/user-authentication/typescript.incl.md index 716f1b714..0469a67a2 100644 --- a/teams.md/src/components/include/migrations/botbuilder/user-authentication/typescript.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/user-authentication/typescript.incl.md @@ -170,4 +170,5 @@ ``` + diff --git a/teams.md/src/components/include/migrations/slack-bolt/typescript.incl.md b/teams.md/src/components/include/migrations/slack-bolt/typescript.incl.md index dfd05d9b7..e3db4065a 100644 --- a/teams.md/src/components/include/migrations/slack-bolt/typescript.incl.md +++ b/teams.md/src/components/include/migrations/slack-bolt/typescript.incl.md @@ -13,389 +13,389 @@ First, let's configure the `App` class in Teams JS. This is equivalent to Slack +````ts + // Setup app + // highlight-error-start + import { App } from '@slack/bolt'; + + const app = new App({ + signingSecret: process.env.SLACK_SIGNING_SECRET, + clientId: process.env.SLACK_CLIENT_ID, + clientSecret: process.env.SLACK_CLIENT_SECRET, + scopes: [ + "channels:manage", + "channels:read", + "chat:write", + "groups:read", + "incoming-webhook", + ], + installerOptions: { + authVersion: "v2", + directInstall: false, + installPath: "/slack/install", + metadata: "", + redirectUriPath: "/slack/oauth_redirect", + stateVerification: "true", + /** + * Example pages to navigate to on certain callbacks. + */ + callbackOptions: { + success: (installation, installUrlOptions, req, res) => { + res.send("The installation succeeded!"); + }, + failure: (error, installUrlOptions, req, res) => { + res.send("Something strange happened..."); + }, + }, + /** + * Example validation of installation options using a random state and an + * expiration time between requests. + */ + stateStore: { + generateStateParam: async (installUrlOptions, now) => { + const state = randomStringGenerator(); + const value = { options: installUrlOptions, now: now.toJSON() }; + await database.set(state, value); + return state; + }, + verifyStateParam: async (now, state) => { + const value = await database.get(state); + const generated = new Date(value.now); + const seconds = Math.floor( + (now.getTime() - generated.getTime()) / 1000, + ); + if (seconds > 600) { + throw new Error("The state expired after 10 minutes!"); + } + return value.options; + }, + }, + }, + }); + // highlight-error-end + // highlight-success-start + import { App } from '@microsoft/teams.apps'; + + // Define app + const app = new App({ + clientId: process.env.ENTRA_APP_CLIENT_ID!, + clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!, + tenantId: process.env.ENTRA_TENANT_ID!, + }); + // highlight-success-end + + // App starts local server with route for /api/messages + (async () => { + await app.start(); + })(); + ``` + + + + ```ts + import { App } from '@slack/bolt'; + + const app = new App({ + signingSecret: process.env.SLACK_SIGNING_SECRET, + clientId: process.env.SLACK_CLIENT_ID, + clientSecret: process.env.SLACK_CLIENT_SECRET, + scopes: [ + "channels:manage", + "channels:read", + "chat:write", + "groups:read", + "incoming-webhook", + ], + installerOptions: { + authVersion: "v2", + directInstall: false, + installPath: "/slack/install", + metadata: "", + redirectUriPath: "/slack/oauth_redirect", + stateVerification: "true", + /** + * Example pages to navigate to on certain callbacks. + */ + callbackOptions: { + success: (installation, installUrlOptions, req, res) => { + res.send("The installation succeeded!"); + }, + failure: (error, installUrlOptions, req, res) => { + res.send("Something strange happened..."); + }, + }, + /** + * Example validation of installation options using a random state and an + * expiration time between requests. + */ + stateStore: { + generateStateParam: async (installUrlOptions, now) => { + const state = randomStringGenerator(); + const value = { options: installUrlOptions, now: now.toJSON() }; + await database.set(state, value); + return state; + }, + verifyStateParam: async (now, state) => { + const value = await database.get(state); + const generated = new Date(value.now); + const seconds = Math.floor( + (now.getTime() - generated.getTime()) / 1000, + ); + if (seconds > 600) { + throw new Error("The state expired after 10 minutes!"); + } + return value.options; + }, + }, + }, + }); + + // App starts local server with route for /slack/events + (async () => { + await app.start(); + })(); + ``` + + + ```ts - // Setup app - // highlight-error-start - import { App } from '@slack/bolt'; - - const app = new App({ - signingSecret: process.env.SLACK_SIGNING_SECRET, - clientId: process.env.SLACK_CLIENT_ID, - clientSecret: process.env.SLACK_CLIENT_SECRET, - scopes: [ - "channels:manage", - "channels:read", - "chat:write", - "groups:read", - "incoming-webhook", - ], - installerOptions: { - authVersion: "v2", - directInstall: false, - installPath: "/slack/install", - metadata: "", - redirectUriPath: "/slack/oauth_redirect", - stateVerification: "true", - /** - * Example pages to navigate to on certain callbacks. - */ - callbackOptions: { - success: (installation, installUrlOptions, req, res) => { - res.send("The installation succeeded!"); - }, - failure: (error, installUrlOptions, req, res) => { - res.send("Something strange happened..."); - }, - }, - /** - * Example validation of installation options using a random state and an - * expiration time between requests. - */ - stateStore: { - generateStateParam: async (installUrlOptions, now) => { - const state = randomStringGenerator(); - const value = { options: installUrlOptions, now: now.toJSON() }; - await database.set(state, value); - return state; - }, - verifyStateParam: async (now, state) => { - const value = await database.get(state); - const generated = new Date(value.now); - const seconds = Math.floor( - (now.getTime() - generated.getTime()) / 1000, - ); - if (seconds > 600) { - throw new Error("The state expired after 10 minutes!"); - } - return value.options; - }, - }, - }, - }); - // highlight-error-end - // highlight-success-start - import { App } from '@microsoft/teams.apps'; - - // Define app - const app = new App({ - clientId: process.env.ENTRA_APP_CLIENT_ID!, - clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!, - tenantId: process.env.ENTRA_TENANT_ID!, - }); - // highlight-success-end - - // App starts local server with route for /api/messages - (async () => { - await app.start(); - })(); - ``` - - - - ```ts - import { App } from '@slack/bolt'; - - const app = new App({ - signingSecret: process.env.SLACK_SIGNING_SECRET, - clientId: process.env.SLACK_CLIENT_ID, - clientSecret: process.env.SLACK_CLIENT_SECRET, - scopes: [ - "channels:manage", - "channels:read", - "chat:write", - "groups:read", - "incoming-webhook", - ], - installerOptions: { - authVersion: "v2", - directInstall: false, - installPath: "/slack/install", - metadata: "", - redirectUriPath: "/slack/oauth_redirect", - stateVerification: "true", - /** - * Example pages to navigate to on certain callbacks. - */ - callbackOptions: { - success: (installation, installUrlOptions, req, res) => { - res.send("The installation succeeded!"); - }, - failure: (error, installUrlOptions, req, res) => { - res.send("Something strange happened..."); - }, - }, - /** - * Example validation of installation options using a random state and an - * expiration time between requests. - */ - stateStore: { - generateStateParam: async (installUrlOptions, now) => { - const state = randomStringGenerator(); - const value = { options: installUrlOptions, now: now.toJSON() }; - await database.set(state, value); - return state; - }, - verifyStateParam: async (now, state) => { - const value = await database.get(state); - const generated = new Date(value.now); - const seconds = Math.floor( - (now.getTime() - generated.getTime()) / 1000, - ); - if (seconds > 600) { - throw new Error("The state expired after 10 minutes!"); - } - return value.options; - }, - }, - }, - }); - - // App starts local server with route for /slack/events - (async () => { - await app.start(); - })(); - ``` - - - - ```ts - import { App } from '@microsoft/teams.apps'; - - // Define app - const app = new App({ - clientId: process.env.ENTRA_APP_CLIENT_ID!, - clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!, - tenantId: process.env.ENTRA_TENANT_ID!, - }); - - // App starts local server with route for /api/messages - // To reuse your restify or other server, - // create a custom `HttpPlugin`. - (async () => { - await app.start(); - })(); - ``` - + import { App } from '@microsoft/teams.apps'; + + // Define app + const app = new App({ + clientId: process.env.ENTRA_APP_CLIENT_ID!, + clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!, + tenantId: process.env.ENTRA_TENANT_ID!, + }); + + // App starts local server with route for /api/messages + // To reuse your restify or other server, + // create a custom `HttpPlugin`. + (async () => { + await app.start(); + })(); + ``` + - + + + ```ts + // triggers user sends "hi" or "@bot hi" + // highlight-error-start + app.message("hi", async ({ message, say }) => { + // Handle only newly posted messages here + if (message.subtype) return; + await say(`Hello, <@${message.user}>`); + }); + // highlight-error-end + // highlight-success-start + app.message("hi", async ({ send, activity }) => { + await send(`Hello, ${activity.from.name}!`); + }); + // highlight-success-end + // listen for ANY message to be received + // highlight-error-start + app.message(async ({ message, say }) => { + // Handle only newly posted messages here + if (message.subtype) return; + // echo back users request + await say(`you said: ${message.text}`); + }); + // highlight-error-end + // highlight-success-start + app.on('message', async ({ send, activity }) => { + // echo back users request + await send(`you said: ${activity.text}`); + }); + // highlight-success-end + ``` + + - ```ts - // triggers user sends "hi" or "@bot hi" - // highlight-error-start - app.message("hi", async ({ message, say }) => { - // Handle only newly posted messages here - if (message.subtype) return; - await say(`Hello, <@${message.user}>`); - }); - // highlight-error-end - // highlight-success-start - app.message("hi", async ({ send, activity }) => { - await send(`Hello, ${activity.from.name}!`); - }); - // highlight-success-end - // listen for ANY message to be received - // highlight-error-start - app.message(async ({ message, say }) => { - // Handle only newly posted messages here - if (message.subtype) return; - // echo back users request - await say(`you said: ${message.text}`); - }); - // highlight-error-end - // highlight-success-start - app.on('message', async ({ send, activity }) => { - // echo back users request - await send(`you said: ${activity.text}`); - }); - // highlight-success-end - ``` - - - - ```ts - // triggers when user sends a message containing "hi" - app.message("hi", async ({ message, say }) => { - // Handle only newly posted messages here - if (message.subtype) return; - await say(`Hello, <@${message.user}>`); - }); - // listen for ANY message - app.message(async ({ message, say }) => { - // Handle only newly posted messages here - if (message.subtype) return; - // echo back users request - await say(`you said: ${message.text}`); - }); - ``` - - - - ```ts - // triggers when user sends "hi" or "@bot hi" - app.message("hi", async ({ send, activity }) => { - await send(`Hello, ${activity.from.name}!`); - }); - // listen for ANY message to be received - app.on('message', async ({ send, activity }) => { - // echo back users request - await send(`you said: ${activity.text}`); - }); - ``` - + ```ts + // triggers when user sends a message containing "hi" + app.message("hi", async ({ message, say }) => { + // Handle only newly posted messages here + if (message.subtype) return; + await say(`Hello, <@${message.user}>`); + }); + // listen for ANY message + app.message(async ({ message, say }) => { + // Handle only newly posted messages here + if (message.subtype) return; + // echo back users request + await say(`you said: ${message.text}`); + }); + ``` + + + + ```ts + // triggers when user sends "hi" or "@bot hi" + app.message("hi", async ({ send, activity }) => { + await send(`Hello, ${activity.from.name}!`); + }); + // listen for ANY message to be received + app.on('message', async ({ send, activity }) => { + // echo back users request + await send(`you said: ${activity.text}`); + }); + ``` + - + - ```ts - // highlight-error-start - app.message('card', async (client) => { - await say({ - blocks: [ - { - type: 'section', - text: { - type: 'plain_text', - text: 'Hello, world!', - }, - }, - ], - }); - }); - // highlight-error-end - // highlight-success-start - import { Card, TextBlock } from '@microsoft/teams.cards'; - - app.message('/card', async ({ send }) => { - await send( - new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })) - .withOptions({ - width: 'Full', - }) - ); - }); - // highlight-success-end - ``` - - - For existing cards like this, the simplest way to convert that to Teams SDK is this: - - ```ts - app.message('card', async (client) => { - await say({ - blocks: [ - { - type: 'section', - text: { - type: 'plain_text', - text: 'Hello, world!', - }, - }, - ], - }); - }); - ``` - - - - For a more thorough port, you could also do the following: - - ```ts - import { Card, TextBlock } from '@microsoft/teams.cards'; - - app.message('/card', async ({ send }) => { + ```ts + // highlight-error-start + app.message('card', async (client) => { + await say({ + blocks: [ + { + type: 'section', + text: { + type: 'plain_text', + text: 'Hello, world!', + }, + }, + ], + }); + }); + // highlight-error-end + // highlight-success-start + import { Card, TextBlock } from '@microsoft/teams.cards'; + + app.message('/card', async ({ send }) => { await send( - new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })).withOptions({ - width: 'Full', - }) + new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })) + .withOptions({ + width: 'Full', + }) ); - }); - ``` + }); + // highlight-success-end + ``` + + + For existing cards like this, the simplest way to convert that to Teams SDK is this: - + ```ts + app.message('card', async (client) => { + await say({ + blocks: [ + { + type: 'section', + text: { + type: 'plain_text', + text: 'Hello, world!', + }, + }, + ], + }); + }); + ``` + + + + For a more thorough port, you could also do the following: + + ```ts + import { Card, TextBlock } from '@microsoft/teams.cards'; + + app.message('/card', async ({ send }) => { + await send( + new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })).withOptions({ + width: 'Full', + }) + ); + }); + ``` + + - + - ```ts - // highlight-error-start - // TODO: Configure App class with user OAuth permissions and install app for user - - app.message('me', async ({ client, message }) => { - const me = await client.users.info({ user: message.user }); - await client.send(JSON.stringify(me)); - }); - // highlight-error-end - // highlight-success-start - import { App } from '@microsoft/teams.apps'; - import * as endpoints from '@microsoft/teams.graph-endpoints'; - - const app = new App({ - // ... rest of App config - oauth: { - // The key here should match the OAuth Connection setting - // defined in your Azure Bot resource. - defaultConnectionName: 'graph', - }, - }); - - app.message('me', async ({ signin, userGraph, send }) => { - if (!await signin()) { - return; - } - const me = await userGraph.call(endpoints.me.get); - await send(JSON.stringify(me)); - }); - // highlight-success-end - ``` - - - - - ```ts - // TODO: Configure App class with user OAuth permissions and install app for user - - app.message('me', async ({ client, message }) => { - const me = await client.users.info({ user: message.user }); - await client.send(JSON.stringify(me)); - }); - ``` - - - - - ```ts - import { App } from '@microsoft/teams.apps'; - import * as endpoints from '@microsoft/teams.graph-endpoints'; - - const app = new App({ - // ... rest of App config - oauth: { - // The key here should match the OAuth Connection setting - // defined in your Azure Bot resource. - defaultConnectionName: 'graph', - }, - }); - - app.message('me', async ({ signin, userGraph, send }) => { - if (!await signin()) { - return; - } - const me = await userGraph.call(endpoints.me.get); - await send(JSON.stringify(me)); - }); - ``` - - + ```ts + // highlight-error-start + // TODO: Configure App class with user OAuth permissions and install app for user + + app.message('me', async ({ client, message }) => { + const me = await client.users.info({ user: message.user }); + await client.send(JSON.stringify(me)); + }); + // highlight-error-end + // highlight-success-start + import { App } from '@microsoft/teams.apps'; + import * as endpoints from '@microsoft/teams.graph-endpoints'; + + const app = new App({ + // ... rest of App config + oauth: { + // The key here should match the OAuth Connection setting + // defined in your Azure Bot resource. + defaultConnectionName: 'graph', + }, + }); + + app.message('me', async ({ signin, userGraph, send }) => { + if (!await signin()) { + return; + } + const me = await userGraph.call(endpoints.me.get); + await send(JSON.stringify(me)); + }); + // highlight-success-end + ``` + + + + + ```ts + // TODO: Configure App class with user OAuth permissions and install app for user + + app.message('me', async ({ client, message }) => { + const me = await client.users.info({ user: message.user }); + await client.send(JSON.stringify(me)); + }); + ``` + + + + + ```ts + import { App } from '@microsoft/teams.apps'; + import * as endpoints from '@microsoft/teams.graph-endpoints'; + + const app = new App({ + // ... rest of App config + oauth: { + // The key here should match the OAuth Connection setting + // defined in your Azure Bot resource. + defaultConnectionName: 'graph', + }, + }); + + app.message('me', async ({ signin, userGraph, send }) => { + if (!await signin()) { + return; + } + const me = await userGraph.call(endpoints.me.get); + await send(JSON.stringify(me)); + }); + ``` + + @@ -404,30 +404,30 @@ First, let's configure the `App` class in Teams JS. This is equivalent to Slack import { App } from '@microsoft/teams.apps'; const app = new App({ - // ... rest of App config - oauth: { - // The key here should match the OAuth Connection setting - // defined in your Azure Bot resource. - defaultConnectionName: 'custom', - }, + // ... rest of App config + oauth: { + // The key here should match the OAuth Connection setting + // defined in your Azure Bot resource. + defaultConnectionName: 'custom', + }, }); app.message('me', async ({ activity, signin, token, send }) => { - // In production, it is probably better to implement a local cache. - // (e.g. \`activity.from.id\` <-> token). - // Otherwise this triggers an API call to Azure Token Service on every inbound message. - if (!await signin()) { - return; - } - - // Call external API - const response = await fetch('https://example.com/api/helloworld', { - method: 'POST', - headers: { - "Authorization": token, - }, - }); - const result = await response.json(); - await send(JSON.stringify(result)); + // In production, it is probably better to implement a local cache. + // (e.g. \`activity.from.id\` <-> token). + // Otherwise this triggers an API call to Azure Token Service on every inbound message. + if (!await signin()) { + return; + } + + // Call external API + const response = await fetch('https://example.com/api/helloworld', { + method: 'POST', + headers: { + "Authorization": token, + }, + }); + const result = await response.json(); + await send(JSON.stringify(result)); }); -``` +```` diff --git a/teams.md/src/pages/index.tsx b/teams.md/src/pages/index.tsx index ba6bb18de..7ecec02e8 100644 --- a/teams.md/src/pages/index.tsx +++ b/teams.md/src/pages/index.tsx @@ -1,5 +1,5 @@ import { Redirect } from '@docusaurus/router'; export default function Home() { - return ; + return ; } diff --git a/teams.md/src/scripts/scaffold.js b/teams.md/src/scripts/scaffold.js index 39dd2d632..7607f140b 100644 --- a/teams.md/src/scripts/scaffold.js +++ b/teams.md/src/scripts/scaffold.js @@ -15,18 +15,21 @@ function createFileIfNotExists(filePath, content = '') { } } - function scaffold(userInput) { // If path starts with src/ but is not a valid base, fail - if (/^src\//.test(userInput) && - !/^src\/pages\/templates\//.test(userInput) && - !/^src\/components\/include\//.test(userInput)) { + if ( + /^src\//.test(userInput) && + !/^src\/pages\/templates\//.test(userInput) && + !/^src\/components\/include\//.test(userInput) + ) { console.error('Error: Path is outside of allowed base directories.'); process.exit(1); } // Normalize input - let relPath = userInput.replace(/^src\/(pages\/templates|components\/include)\/?/, '').replace(/^[/.]+/, ''); + let relPath = userInput + .replace(/^src\/(pages\/templates|components\/include)\/?/, '') + .replace(/^[/.]+/, ''); let isTemplates = false; let isInclude = false; // Detect if user input is for templates or include @@ -80,7 +83,7 @@ function scaffold(userInput) { const includeTarget = walkAndCreate(includeBase, relPath, (dir) => { createFileIfNotExists(path.join(dir, 'typescript.incl.md'), '# Typescript Include\n'); }); - return {templatesTarget, includeTarget}; + return { templatesTarget, includeTarget }; } if (isTemplates) { const templatesBase = path.join(__dirname, '../pages/templates'); @@ -88,14 +91,14 @@ function scaffold(userInput) { createFileIfNotExists(path.join(dir, '_category.json'), '{\n "label": "New Category"\n}\n'); createFileIfNotExists(path.join(dir, 'README.mdx'), '# New Template\n'); }); - return {templatesTarget}; + return { templatesTarget }; } if (isInclude) { const includeBase = path.join(__dirname, '../components/include'); const includeTarget = walkAndCreate(includeBase, relPath, (dir) => { createFileIfNotExists(path.join(dir, 'typescript.incl.md'), '# Typescript Include\n'); }); - return {includeTarget}; + return { includeTarget }; } } @@ -107,14 +110,18 @@ function isValidPath(p) { } if (!isValidPath(userPath)) { - console.error('Error: Invalid path. Use a relative path like "templates/new/two" or "include/new/two". Do not start with a slash or use "..".'); + console.error( + 'Error: Invalid path. Use a relative path like "templates/new/two" or "include/new/two". Do not start with a slash or use "..".' + ); process.exit(1); } const created = scaffold(userPath); if (created) { if (created.templatesTarget && created.includeTarget) { - console.log(`Scaffolded:\n Template: ${created.templatesTarget}\n Include: ${created.includeTarget}`); + console.log( + `Scaffolded:\n Template: ${created.templatesTarget}\n Include: ${created.includeTarget}` + ); } else if (created.templatesTarget) { console.log(`Scaffolded Template: ${created.templatesTarget}`); } else if (created.includeTarget) { diff --git a/teams.md/src/utils/normalizePath.ts b/teams.md/src/utils/normalizePath.ts index 40ba288ea..19456cb0c 100644 --- a/teams.md/src/utils/normalizePath.ts +++ b/teams.md/src/utils/normalizePath.ts @@ -2,5 +2,5 @@ * For cross-platform compatibility, normalize paths to use forward slash */ export default function normalizePath(path: string) { -return path.replace(/\\/g, '/'); -} \ No newline at end of file + return path.replace(/\\/g, '/'); +} diff --git a/teams.md/src/utils/pageAvailability.ts b/teams.md/src/utils/pageAvailability.ts index 50d209bf5..a8a23ac54 100644 --- a/teams.md/src/utils/pageAvailability.ts +++ b/teams.md/src/utils/pageAvailability.ts @@ -9,7 +9,10 @@ let missingPagesCache: LanguageAvailabilityMap | null = null; * @param language - The target language to check * @returns Promise - true: page available; else false */ -export async function isPageAvailableForLanguage(pagePath: string, language: Language): Promise { +export async function isPageAvailableForLanguage( + pagePath: string, + language: Language +): Promise { if (!missingPagesCache) { try { const response = await fetch('/teams-sdk/missing-pages.json'); diff --git a/teams.md/static/scripts/clarity.js b/teams.md/static/scripts/clarity.js index 087ef74e6..2e906a818 100644 --- a/teams.md/static/scripts/clarity.js +++ b/teams.md/static/scripts/clarity.js @@ -1,5 +1,12 @@ -(function(c,l,a,r,i,t,y){ - c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; - t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; - y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); -})(window, document, "clarity", "script", "rdeztc00ot"); \ No newline at end of file +(function (c, l, a, r, i, t, y) { + c[a] = + c[a] || + function () { + (c[a].q = c[a].q || []).push(arguments); + }; + t = l.createElement(r); + t.async = 1; + t.src = 'https://www.clarity.ms/tag/' + i; + y = l.getElementsByTagName(r)[0]; + y.parentNode.insertBefore(t, y); +})(window, document, 'clarity', 'script', 'rdeztc00ot'); From 815acfd93e6c64a75be47b433cb834a711bdad47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 23:33:24 +0000 Subject: [PATCH 4/4] Revert prettier changes to files with MDX syntax errors Co-authored-by: rido-min <14916339+rido-min@users.noreply.github.com> --- package-lock.json | 38 +- .../user-authentication/python.incl.md | 14 +- .../user-authentication/typescript.incl.md | 16 +- .../botbuilder/the-api-client/csharp.incl.md | 78 +- .../botbuilder/the-api-client/python.incl.md | 29 +- .../the-api-client/typescript.incl.md | 92 +-- .../migrations/slack-bolt/typescript.incl.md | 768 +++++++++--------- 7 files changed, 522 insertions(+), 513 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1803ce060..f78e9f2a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -224,6 +224,7 @@ "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.41.0.tgz", "integrity": "sha512-G9I2atg1ShtFp0t7zwleP6aPS4DcZvsV4uoQOripp16aR6VJzbEnKFPLW4OFXzX7avgZSpYeBAS+Zx4FOgmpPw==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/client-common": "5.41.0", "@algolia/requester-browser-xhr": "5.41.0", @@ -371,6 +372,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2199,6 +2201,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2221,6 +2224,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2330,6 +2334,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2751,6 +2756,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3641,6 +3647,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -3909,6 +3916,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz", "integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==", "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/mdx-loader": "3.9.2", "@docusaurus/module-type-aliases": "3.9.2", @@ -4921,6 +4929,7 @@ "node_modules/@mdx-js/react": { "version": "3.1.0", "license": "MIT", + "peer": true, "dependencies": { "@types/mdx": "^2.0.0" }, @@ -5267,6 +5276,7 @@ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -5828,6 +5838,7 @@ "node_modules/@types/react": { "version": "19.1.5", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -6118,6 +6129,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6181,6 +6193,7 @@ "node_modules/ajv": { "version": "8.17.1", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6222,6 +6235,7 @@ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.41.0.tgz", "integrity": "sha512-9E4b3rJmYbBkn7e3aAPt1as+VVnRhsR4qwRRgOzpeyz4PAOuwKh0HI4AN6mTrqK0S0M9fCCSTOUnuJ8gPY/tvA==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/abtesting": "1.7.0", "@algolia/client-abtesting": "5.41.0", @@ -6699,6 +6713,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -6974,6 +6989,7 @@ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", @@ -7645,6 +7661,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -7954,6 +7971,7 @@ "node_modules/cytoscape": { "version": "3.32.0", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -8357,6 +8375,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -9442,6 +9461,7 @@ "node_modules/file-loader/node_modules/ajv": { "version": "6.12.6", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -13790,6 +13810,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14338,6 +14359,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -15241,6 +15263,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -16024,6 +16047,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -16033,6 +16057,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -16078,6 +16103,7 @@ "name": "@docusaurus/react-loadable", "version": "6.0.0", "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" }, @@ -16102,7 +16128,6 @@ "node_modules/react-router": { "version": "7.6.0", "license": "MIT", - "peer": true, "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -16175,7 +16200,6 @@ "node_modules/react-router/node_modules/cookie": { "version": "1.0.2", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -17088,8 +17112,7 @@ }, "node_modules/set-cookie-parser": { "version": "2.7.1", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/set-function-length": { "version": "1.2.2", @@ -17842,7 +17865,8 @@ }, "node_modules/tslib": { "version": "2.8.1", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.20.6", @@ -17996,6 +18020,7 @@ "version": "5.8.2", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18377,6 +18402,7 @@ "node_modules/url-loader/node_modules/ajv": { "version": "6.12.6", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -18594,6 +18620,7 @@ "node_modules/webpack": { "version": "5.99.9", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -19179,6 +19206,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/teams.md/src/components/include/in-depth-guides/user-authentication/python.incl.md b/teams.md/src/components/include/in-depth-guides/user-authentication/python.incl.md index 95f1956a1..c1fecee3a 100644 --- a/teams.md/src/components/include/in-depth-guides/user-authentication/python.incl.md +++ b/teams.md/src/components/include/in-depth-guides/user-authentication/python.incl.md @@ -87,19 +87,17 @@ async def handle_signout_message(ctx: ActivityContext[MessageActivity]): ``` - import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; ## Regional Configs - You may be building a regional bot that is deployed in a specific Azure region (such as West Europe, East US, etc.) rather than global. This is important for organizations that have data residency requirements or want to reduce latency by keeping data and authentication flows within a specific area. These examples use West Europe, but follow the equivalent for other regions. -To configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. +To configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. 1. Deploy a new App Registration in `westeurope`. 2. Deploy and link a new Enterprise Application (Service Principal) on Microsoft Entra in `westeurope`. @@ -109,8 +107,8 @@ To configure a new regional bot in Azure, you must setup your resoures in the de ![Authentication Tab](/screenshots/regional-auth.png) 5. In your `.env` file (or wherever you set your environment variables), add your `OAUTH_URL`. For example: - `OAUTH_URL=https://europe.token.botframework.com` - +`OAUTH_URL=https://europe.token.botframework.com` + To configure a new regional bot with ATK, you will need to make a few updates. Note that this assumes you have not yet deployed the bot previously. @@ -119,6 +117,6 @@ To configure a new regional bot with ATK, you will need to make a few updates. N 2. In `manifest.json`, in `validDomains`, `*.botframework.com` should be replaced by `europe.token.botframework.com` 3. In `aad.manifest.json`, replace `https://token.botframework.com/.auth/web/redirect` with `https://europe.token.botframework.com/.auth/web/redirect` 4. In your `.env` file, add your `OAUTH_URL`. For example: - `OAUTH_URL=https://europe.token.botframework.com`. - - +`OAUTH_URL=https://europe.token.botframework.com`. + + \ No newline at end of file diff --git a/teams.md/src/components/include/in-depth-guides/user-authentication/typescript.incl.md b/teams.md/src/components/include/in-depth-guides/user-authentication/typescript.incl.md index cd4b95e13..2df8cf15f 100644 --- a/teams.md/src/components/include/in-depth-guides/user-authentication/typescript.incl.md +++ b/teams.md/src/components/include/in-depth-guides/user-authentication/typescript.incl.md @@ -51,7 +51,7 @@ app.event('signin', async ({ send, token }) => { import * as endpoints from '@microsoft/teams.graph-endpoints'; app.message('/whoami', async ({ send, userGraph, signin }) => { - if (!(await signin())) { + if (!await signin()) { return; } const me = await userGraph.call(endpoints.me.get); @@ -82,19 +82,17 @@ app.message('/signout', async ({ send, signout, isSignedIn }) => { ``` - import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; ## Regional Configs - You may be building a regional bot that is deployed in a specific Azure region (such as West Europe, East US, etc.) rather than global. This is important for organizations that have data residency requirements or want to reduce latency by keeping data and authentication flows within a specific area. These examples use West Europe, but follow the equivalent for other regions. -To configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. +To configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. 1. Deploy a new App Registration in `westeurope`. 2. Deploy and link a new Enterprise Application (Service Principal) on Microsoft Entra in `westeurope`. @@ -104,8 +102,8 @@ To configure a new regional bot in Azure, you must setup your resoures in the de ![Authentication Tab](/screenshots/regional-auth.png) 5. In your `.env` file (or wherever you set your environment variables), add your `OAUTH_URL`. For example: - `OAUTH_URL=https://europe.token.botframework.com` - +`OAUTH_URL=https://europe.token.botframework.com` + To configure a new regional bot with ATK, you will need to make a few updates. Note that this assumes you have not yet deployed the bot previously. @@ -114,6 +112,6 @@ To configure a new regional bot with ATK, you will need to make a few updates. N 2. In `manifest.json`, in `validDomains`, `*.botframework.com` should be replaced by `europe.token.botframework.com` 3. In `aad.manifest.json`, replace `https://token.botframework.com/.auth/web/redirect` with `https://europe.token.botframework.com/.auth/web/redirect` 4. In your `.env` file, add your `OAUTH_URL`. For example: - `OAUTH_URL=https://europe.token.botframework.com` - - +`OAUTH_URL=https://europe.token.botframework.com` + + \ No newline at end of file diff --git a/teams.md/src/components/include/migrations/botbuilder/the-api-client/csharp.incl.md b/teams.md/src/components/include/migrations/botbuilder/the-api-client/csharp.incl.md index 19b773f93..ff9882939 100644 --- a/teams.md/src/components/include/migrations/botbuilder/the-api-client/csharp.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/the-api-client/csharp.incl.md @@ -10,57 +10,54 @@ // highlight-success-line + using Microsoft.Teams.Apps; -// highlight-error-start - -- public class MyActivityHandler : ActivityHandler -- { + // highlight-error-start +- public class MyActivityHandler : ActivityHandler +- { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken); - } -- } +- } // highlight-error-end // highlight-success-start - -* var teams = app.UseTeams(); -* teams.OnMessage(async (context) => -* { -* var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id); -* }); ++ var teams = app.UseTeams(); ++ teams.OnMessage(async (context) => ++ { ++ var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id); ++ }); // highlight-success-end - -```` - - - ```csharp showLineNumbers - using Microsoft.Bot.Builder; - using Microsoft.Bot.Builder.Teams; - - public class MyActivityHandler : TeamsActivityHandler - { - protected override async Task OnMessageActivityAsync( - ITurnContext turnContext, - CancellationToken cancellationToken) - { - // highlight-next-line - var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken); - } - } ``` - - - ```csharp showLineNumbers - using Microsoft.Teams.Apps; + + + ```csharp showLineNumbers + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Teams; - app.OnMessage(async (context) => - { - // highlight-next-line - var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id); - }); - ``` - + public class MyActivityHandler : TeamsActivityHandler + { + protected override async Task OnMessageActivityAsync( + ITurnContext turnContext, + CancellationToken cancellationToken) + { + // highlight-next-line + var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken); + } + } + ``` + + + ```csharp showLineNumbers + using Microsoft.Teams.Apps; + + app.OnMessage(async (context) => + { + // highlight-next-line + var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id); + }); + ``` + @@ -71,4 +68,3 @@ | `TeamsInfo.GetTeamDetailsAsync(context, teamId)` | `Api.Teams.GetByIdAsync(teamId)` | | `TeamsInfo.GetMeetingInfoAsync(context, meetingId)` | `Api.Meetings.GetByIdAsync(meetingId)` | | `TeamsInfo.SendMessageToTeamsChannelAsync(context, teamId, message)` | `Api.Conversations.CreateAsync(CreateRequest)` then `Api.Conversations.Activities.CreateAsync(conversationId, activity)` | -```` diff --git a/teams.md/src/components/include/migrations/botbuilder/the-api-client/python.incl.md b/teams.md/src/components/include/migrations/botbuilder/the-api-client/python.incl.md index 8a920f177..cd76e3a13 100644 --- a/teams.md/src/components/include/migrations/botbuilder/the-api-client/python.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/the-api-client/python.incl.md @@ -11,21 +11,17 @@ + from microsoft_teams.apps import ActivityContext + from microsoft_teams.api import MessageActivity -# highlight-error-start - -- class MyActivityHandler(ActivityHandler): + # highlight-error-start +- class MyActivityHandler(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - members = await TeamsInfo.get_members(turn_context) # highlight-error-end # highlight-success-start - -* @app.on_message -* async def on_message(context: ActivityContext[MessageActivity]): -* members = await context.api.conversations.members(context.activity.conversation.id).get_all() - ++ @app.on_message ++ async def on_message(context: ActivityContext[MessageActivity]): ++ members = await context.api.conversations.members(context.activity.conversation.id).get_all() # highlight-success-end - - ```` + ``` ```python showLineNumbers @@ -49,14 +45,13 @@ members = await context.api.conversations.members(context.activity.conversation.id).get() ``` - - ```` + -| BotBuilder (TeamsInfo) | Teams SDK (ApiClient) | -| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| `TeamsInfo.getMembers(context, user_id)` | `api.conversations.members(conversation_id).get(user_id)` | -| `TeamsInfo.get_team_details(context, team_id)` | `api.teams.get_by_id(team_id)` | -| `TeamsInfo.get_meeting_info(context, meeting_id)` | `api.meetings.get_by_id(meeting_id)` | +| BotBuilder (TeamsInfo) | Teams SDK (ApiClient) | +|------------------------|----------------------| +| `TeamsInfo.getMembers(context, user_id)` | `api.conversations.members(conversation_id).get(user_id)` | +| `TeamsInfo.get_team_details(context, team_id)` | `api.teams.get_by_id(team_id)` | +| `TeamsInfo.get_meeting_info(context, meeting_id)` | `api.meetings.get_by_id(meeting_id)` | | `TeamsInfo.send_message_to_teams_channel(context, team_id, message)` | `api.conversations.create(CreateConversationParams)` then `api.conversations.activities(conversation_id).create(activity)` | diff --git a/teams.md/src/components/include/migrations/botbuilder/the-api-client/typescript.incl.md b/teams.md/src/components/include/migrations/botbuilder/the-api-client/typescript.incl.md index f1cf9f1a9..f1ea7ce86 100644 --- a/teams.md/src/components/include/migrations/botbuilder/the-api-client/typescript.incl.md +++ b/teams.md/src/components/include/migrations/botbuilder/the-api-client/typescript.incl.md @@ -13,69 +13,64 @@ // highlight-success-line + import { App } from '@microsoft/teams.apps'; -// highlight-error-start - -- const auth = new ConfigurationBotFrameworkAuthentication(process.env); -- const adapter = new CloudAdapter(auth); + // highlight-error-start +- const auth = new ConfigurationBotFrameworkAuthentication(process.env); +- const adapter = new CloudAdapter(auth); // highlight-error-end // highlight-success-line ++ const app = new App(); -* const app = new App(); - -// highlight-error-start - -- export class ActivityHandler extends TeamsActivityHandler { -- constructor() { + // highlight-error-start +- export class ActivityHandler extends TeamsActivityHandler { +- constructor() { - super(); - this.onMessage(async (context) => { - const members = await TeamsInfo.getMembers(context); - }); -- } -- } +- } +- } // highlight-error-end // highlight-success-start - -* app.on('message', async ({ api, activity }) => { -* const members = await api.conversations.members(activity.conversation.id).get(); -* }); ++ app.on('message', async ({ api, activity }) => { ++ const members = await api.conversations.members(activity.conversation.id).get(); ++ }); // highlight-success-end + ``` + + + ```typescript showLineNumbers + import { + CloudAdapter, + ConfigurationBotFrameworkAuthentication, + TeamsInfo, + } from 'botbuilder'; -```` - - - ```typescript showLineNumbers - import { - CloudAdapter, - ConfigurationBotFrameworkAuthentication, - TeamsInfo, - } from 'botbuilder'; - - const auth = new ConfigurationBotFrameworkAuthentication(process.env); - const adapter = new CloudAdapter(auth); + const auth = new ConfigurationBotFrameworkAuthentication(process.env); + const adapter = new CloudAdapter(auth); - export class ActivityHandler extends TeamsActivityHandler { - constructor() { - super(); - this.onMessage(async (context) => { - // highlight-next-line - const members = await TeamsInfo.getMembers(context); - }); + export class ActivityHandler extends TeamsActivityHandler { + constructor() { + super(); + this.onMessage(async (context) => { + // highlight-next-line + const members = await TeamsInfo.getMembers(context); + }); + } } - } - ``` - - - ```typescript showLineNumbers - import { App } from '@microsoft/teams.apps'; + ``` + + + ```typescript showLineNumbers + import { App } from '@microsoft/teams.apps'; - const app = new App(); + const app = new App(); - app.on('message', async ({ api, activity }) => { - // highlight-next-line - const members = await api.conversations.members(activity.conversation.id).get(); - }); - ``` - + app.on('message', async ({ api, activity }) => { + // highlight-next-line + const members = await api.conversations.members(activity.conversation.id).get(); + }); + ``` + @@ -86,4 +81,3 @@ | `TeamsInfo.getTeamDetails(context, teamId)` | `api.teams.getById(teamId)` | | `TeamsInfo.getMeetingInfo(context, meetingId)` | `api.meetings.getById(meetingId)` | | `TeamsInfo.sendMessageToTeamsChannel(context, teamId, message)` | `api.conversations.create(CreateConversationParams)` then `api.conversations.activities(conversationId).create(activity)` | -```` diff --git a/teams.md/src/components/include/migrations/slack-bolt/typescript.incl.md b/teams.md/src/components/include/migrations/slack-bolt/typescript.incl.md index e3db4065a..dfd05d9b7 100644 --- a/teams.md/src/components/include/migrations/slack-bolt/typescript.incl.md +++ b/teams.md/src/components/include/migrations/slack-bolt/typescript.incl.md @@ -13,389 +13,389 @@ First, let's configure the `App` class in Teams JS. This is equivalent to Slack -````ts - // Setup app - // highlight-error-start - import { App } from '@slack/bolt'; - - const app = new App({ - signingSecret: process.env.SLACK_SIGNING_SECRET, - clientId: process.env.SLACK_CLIENT_ID, - clientSecret: process.env.SLACK_CLIENT_SECRET, - scopes: [ - "channels:manage", - "channels:read", - "chat:write", - "groups:read", - "incoming-webhook", - ], - installerOptions: { - authVersion: "v2", - directInstall: false, - installPath: "/slack/install", - metadata: "", - redirectUriPath: "/slack/oauth_redirect", - stateVerification: "true", - /** - * Example pages to navigate to on certain callbacks. - */ - callbackOptions: { - success: (installation, installUrlOptions, req, res) => { - res.send("The installation succeeded!"); - }, - failure: (error, installUrlOptions, req, res) => { - res.send("Something strange happened..."); - }, - }, - /** - * Example validation of installation options using a random state and an - * expiration time between requests. - */ - stateStore: { - generateStateParam: async (installUrlOptions, now) => { - const state = randomStringGenerator(); - const value = { options: installUrlOptions, now: now.toJSON() }; - await database.set(state, value); - return state; - }, - verifyStateParam: async (now, state) => { - const value = await database.get(state); - const generated = new Date(value.now); - const seconds = Math.floor( - (now.getTime() - generated.getTime()) / 1000, - ); - if (seconds > 600) { - throw new Error("The state expired after 10 minutes!"); - } - return value.options; - }, - }, - }, - }); - // highlight-error-end - // highlight-success-start - import { App } from '@microsoft/teams.apps'; - - // Define app - const app = new App({ - clientId: process.env.ENTRA_APP_CLIENT_ID!, - clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!, - tenantId: process.env.ENTRA_TENANT_ID!, - }); - // highlight-success-end - - // App starts local server with route for /api/messages - (async () => { - await app.start(); - })(); - ``` - - - - ```ts - import { App } from '@slack/bolt'; - - const app = new App({ - signingSecret: process.env.SLACK_SIGNING_SECRET, - clientId: process.env.SLACK_CLIENT_ID, - clientSecret: process.env.SLACK_CLIENT_SECRET, - scopes: [ - "channels:manage", - "channels:read", - "chat:write", - "groups:read", - "incoming-webhook", - ], - installerOptions: { - authVersion: "v2", - directInstall: false, - installPath: "/slack/install", - metadata: "", - redirectUriPath: "/slack/oauth_redirect", - stateVerification: "true", - /** - * Example pages to navigate to on certain callbacks. - */ - callbackOptions: { - success: (installation, installUrlOptions, req, res) => { - res.send("The installation succeeded!"); - }, - failure: (error, installUrlOptions, req, res) => { - res.send("Something strange happened..."); - }, - }, - /** - * Example validation of installation options using a random state and an - * expiration time between requests. - */ - stateStore: { - generateStateParam: async (installUrlOptions, now) => { - const state = randomStringGenerator(); - const value = { options: installUrlOptions, now: now.toJSON() }; - await database.set(state, value); - return state; - }, - verifyStateParam: async (now, state) => { - const value = await database.get(state); - const generated = new Date(value.now); - const seconds = Math.floor( - (now.getTime() - generated.getTime()) / 1000, - ); - if (seconds > 600) { - throw new Error("The state expired after 10 minutes!"); - } - return value.options; - }, - }, - }, - }); - - // App starts local server with route for /slack/events - (async () => { - await app.start(); - })(); - ``` - - - ```ts - import { App } from '@microsoft/teams.apps'; - - // Define app - const app = new App({ - clientId: process.env.ENTRA_APP_CLIENT_ID!, - clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!, - tenantId: process.env.ENTRA_TENANT_ID!, - }); - - // App starts local server with route for /api/messages - // To reuse your restify or other server, - // create a custom `HttpPlugin`. - (async () => { - await app.start(); - })(); - ``` - + // Setup app + // highlight-error-start + import { App } from '@slack/bolt'; + + const app = new App({ + signingSecret: process.env.SLACK_SIGNING_SECRET, + clientId: process.env.SLACK_CLIENT_ID, + clientSecret: process.env.SLACK_CLIENT_SECRET, + scopes: [ + "channels:manage", + "channels:read", + "chat:write", + "groups:read", + "incoming-webhook", + ], + installerOptions: { + authVersion: "v2", + directInstall: false, + installPath: "/slack/install", + metadata: "", + redirectUriPath: "/slack/oauth_redirect", + stateVerification: "true", + /** + * Example pages to navigate to on certain callbacks. + */ + callbackOptions: { + success: (installation, installUrlOptions, req, res) => { + res.send("The installation succeeded!"); + }, + failure: (error, installUrlOptions, req, res) => { + res.send("Something strange happened..."); + }, + }, + /** + * Example validation of installation options using a random state and an + * expiration time between requests. + */ + stateStore: { + generateStateParam: async (installUrlOptions, now) => { + const state = randomStringGenerator(); + const value = { options: installUrlOptions, now: now.toJSON() }; + await database.set(state, value); + return state; + }, + verifyStateParam: async (now, state) => { + const value = await database.get(state); + const generated = new Date(value.now); + const seconds = Math.floor( + (now.getTime() - generated.getTime()) / 1000, + ); + if (seconds > 600) { + throw new Error("The state expired after 10 minutes!"); + } + return value.options; + }, + }, + }, + }); + // highlight-error-end + // highlight-success-start + import { App } from '@microsoft/teams.apps'; + + // Define app + const app = new App({ + clientId: process.env.ENTRA_APP_CLIENT_ID!, + clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!, + tenantId: process.env.ENTRA_TENANT_ID!, + }); + // highlight-success-end + + // App starts local server with route for /api/messages + (async () => { + await app.start(); + })(); + ``` + + + + ```ts + import { App } from '@slack/bolt'; + + const app = new App({ + signingSecret: process.env.SLACK_SIGNING_SECRET, + clientId: process.env.SLACK_CLIENT_ID, + clientSecret: process.env.SLACK_CLIENT_SECRET, + scopes: [ + "channels:manage", + "channels:read", + "chat:write", + "groups:read", + "incoming-webhook", + ], + installerOptions: { + authVersion: "v2", + directInstall: false, + installPath: "/slack/install", + metadata: "", + redirectUriPath: "/slack/oauth_redirect", + stateVerification: "true", + /** + * Example pages to navigate to on certain callbacks. + */ + callbackOptions: { + success: (installation, installUrlOptions, req, res) => { + res.send("The installation succeeded!"); + }, + failure: (error, installUrlOptions, req, res) => { + res.send("Something strange happened..."); + }, + }, + /** + * Example validation of installation options using a random state and an + * expiration time between requests. + */ + stateStore: { + generateStateParam: async (installUrlOptions, now) => { + const state = randomStringGenerator(); + const value = { options: installUrlOptions, now: now.toJSON() }; + await database.set(state, value); + return state; + }, + verifyStateParam: async (now, state) => { + const value = await database.get(state); + const generated = new Date(value.now); + const seconds = Math.floor( + (now.getTime() - generated.getTime()) / 1000, + ); + if (seconds > 600) { + throw new Error("The state expired after 10 minutes!"); + } + return value.options; + }, + }, + }, + }); + + // App starts local server with route for /slack/events + (async () => { + await app.start(); + })(); + ``` + + + + ```ts + import { App } from '@microsoft/teams.apps'; + + // Define app + const app = new App({ + clientId: process.env.ENTRA_APP_CLIENT_ID!, + clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!, + tenantId: process.env.ENTRA_TENANT_ID!, + }); + + // App starts local server with route for /api/messages + // To reuse your restify or other server, + // create a custom `HttpPlugin`. + (async () => { + await app.start(); + })(); + ``` + - - - ```ts - // triggers user sends "hi" or "@bot hi" - // highlight-error-start - app.message("hi", async ({ message, say }) => { - // Handle only newly posted messages here - if (message.subtype) return; - await say(`Hello, <@${message.user}>`); - }); - // highlight-error-end - // highlight-success-start - app.message("hi", async ({ send, activity }) => { - await send(`Hello, ${activity.from.name}!`); - }); - // highlight-success-end - // listen for ANY message to be received - // highlight-error-start - app.message(async ({ message, say }) => { - // Handle only newly posted messages here - if (message.subtype) return; - // echo back users request - await say(`you said: ${message.text}`); - }); - // highlight-error-end - // highlight-success-start - app.on('message', async ({ send, activity }) => { - // echo back users request - await send(`you said: ${activity.text}`); - }); - // highlight-success-end - ``` - - - - ```ts - // triggers when user sends a message containing "hi" - app.message("hi", async ({ message, say }) => { - // Handle only newly posted messages here - if (message.subtype) return; - await say(`Hello, <@${message.user}>`); - }); - // listen for ANY message - app.message(async ({ message, say }) => { - // Handle only newly posted messages here - if (message.subtype) return; - // echo back users request - await say(`you said: ${message.text}`); - }); - ``` - - + - ```ts - // triggers when user sends "hi" or "@bot hi" - app.message("hi", async ({ send, activity }) => { - await send(`Hello, ${activity.from.name}!`); - }); - // listen for ANY message to be received - app.on('message', async ({ send, activity }) => { - // echo back users request - await send(`you said: ${activity.text}`); - }); - ``` - + ```ts + // triggers user sends "hi" or "@bot hi" + // highlight-error-start + app.message("hi", async ({ message, say }) => { + // Handle only newly posted messages here + if (message.subtype) return; + await say(`Hello, <@${message.user}>`); + }); + // highlight-error-end + // highlight-success-start + app.message("hi", async ({ send, activity }) => { + await send(`Hello, ${activity.from.name}!`); + }); + // highlight-success-end + // listen for ANY message to be received + // highlight-error-start + app.message(async ({ message, say }) => { + // Handle only newly posted messages here + if (message.subtype) return; + // echo back users request + await say(`you said: ${message.text}`); + }); + // highlight-error-end + // highlight-success-start + app.on('message', async ({ send, activity }) => { + // echo back users request + await send(`you said: ${activity.text}`); + }); + // highlight-success-end + ``` + + + + ```ts + // triggers when user sends a message containing "hi" + app.message("hi", async ({ message, say }) => { + // Handle only newly posted messages here + if (message.subtype) return; + await say(`Hello, <@${message.user}>`); + }); + // listen for ANY message + app.message(async ({ message, say }) => { + // Handle only newly posted messages here + if (message.subtype) return; + // echo back users request + await say(`you said: ${message.text}`); + }); + ``` + + + + ```ts + // triggers when user sends "hi" or "@bot hi" + app.message("hi", async ({ send, activity }) => { + await send(`Hello, ${activity.from.name}!`); + }); + // listen for ANY message to be received + app.on('message', async ({ send, activity }) => { + // echo back users request + await send(`you said: ${activity.text}`); + }); + ``` + - + - ```ts - // highlight-error-start - app.message('card', async (client) => { - await say({ - blocks: [ - { - type: 'section', - text: { - type: 'plain_text', - text: 'Hello, world!', - }, - }, - ], - }); - }); - // highlight-error-end - // highlight-success-start - import { Card, TextBlock } from '@microsoft/teams.cards'; - - app.message('/card', async ({ send }) => { + ```ts + // highlight-error-start + app.message('card', async (client) => { + await say({ + blocks: [ + { + type: 'section', + text: { + type: 'plain_text', + text: 'Hello, world!', + }, + }, + ], + }); + }); + // highlight-error-end + // highlight-success-start + import { Card, TextBlock } from '@microsoft/teams.cards'; + + app.message('/card', async ({ send }) => { + await send( + new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })) + .withOptions({ + width: 'Full', + }) + ); + }); + // highlight-success-end + ``` + + + For existing cards like this, the simplest way to convert that to Teams SDK is this: + + ```ts + app.message('card', async (client) => { + await say({ + blocks: [ + { + type: 'section', + text: { + type: 'plain_text', + text: 'Hello, world!', + }, + }, + ], + }); + }); + ``` + + + + For a more thorough port, you could also do the following: + + ```ts + import { Card, TextBlock } from '@microsoft/teams.cards'; + + app.message('/card', async ({ send }) => { await send( - new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })) - .withOptions({ - width: 'Full', - }) + new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })).withOptions({ + width: 'Full', + }) ); - }); - // highlight-success-end - ``` - - - For existing cards like this, the simplest way to convert that to Teams SDK is this: + }); + ``` - ```ts - app.message('card', async (client) => { - await say({ - blocks: [ - { - type: 'section', - text: { - type: 'plain_text', - text: 'Hello, world!', - }, - }, - ], - }); - }); - ``` - - - - For a more thorough port, you could also do the following: - - ```ts - import { Card, TextBlock } from '@microsoft/teams.cards'; - - app.message('/card', async ({ send }) => { - await send( - new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })).withOptions({ - width: 'Full', - }) - ); - }); - ``` - - + - - - ```ts - // highlight-error-start - // TODO: Configure App class with user OAuth permissions and install app for user - - app.message('me', async ({ client, message }) => { - const me = await client.users.info({ user: message.user }); - await client.send(JSON.stringify(me)); - }); - // highlight-error-end - // highlight-success-start - import { App } from '@microsoft/teams.apps'; - import * as endpoints from '@microsoft/teams.graph-endpoints'; - - const app = new App({ - // ... rest of App config - oauth: { - // The key here should match the OAuth Connection setting - // defined in your Azure Bot resource. - defaultConnectionName: 'graph', - }, - }); - - app.message('me', async ({ signin, userGraph, send }) => { - if (!await signin()) { - return; - } - const me = await userGraph.call(endpoints.me.get); - await send(JSON.stringify(me)); - }); - // highlight-success-end - ``` - - - - - ```ts - // TODO: Configure App class with user OAuth permissions and install app for user - - app.message('me', async ({ client, message }) => { - const me = await client.users.info({ user: message.user }); - await client.send(JSON.stringify(me)); - }); - ``` - - - + - ```ts - import { App } from '@microsoft/teams.apps'; - import * as endpoints from '@microsoft/teams.graph-endpoints'; - - const app = new App({ - // ... rest of App config - oauth: { - // The key here should match the OAuth Connection setting - // defined in your Azure Bot resource. - defaultConnectionName: 'graph', - }, - }); - - app.message('me', async ({ signin, userGraph, send }) => { - if (!await signin()) { - return; - } - const me = await userGraph.call(endpoints.me.get); - await send(JSON.stringify(me)); - }); - ``` - - + ```ts + // highlight-error-start + // TODO: Configure App class with user OAuth permissions and install app for user + + app.message('me', async ({ client, message }) => { + const me = await client.users.info({ user: message.user }); + await client.send(JSON.stringify(me)); + }); + // highlight-error-end + // highlight-success-start + import { App } from '@microsoft/teams.apps'; + import * as endpoints from '@microsoft/teams.graph-endpoints'; + + const app = new App({ + // ... rest of App config + oauth: { + // The key here should match the OAuth Connection setting + // defined in your Azure Bot resource. + defaultConnectionName: 'graph', + }, + }); + + app.message('me', async ({ signin, userGraph, send }) => { + if (!await signin()) { + return; + } + const me = await userGraph.call(endpoints.me.get); + await send(JSON.stringify(me)); + }); + // highlight-success-end + ``` + + + + + ```ts + // TODO: Configure App class with user OAuth permissions and install app for user + + app.message('me', async ({ client, message }) => { + const me = await client.users.info({ user: message.user }); + await client.send(JSON.stringify(me)); + }); + ``` + + + + + ```ts + import { App } from '@microsoft/teams.apps'; + import * as endpoints from '@microsoft/teams.graph-endpoints'; + + const app = new App({ + // ... rest of App config + oauth: { + // The key here should match the OAuth Connection setting + // defined in your Azure Bot resource. + defaultConnectionName: 'graph', + }, + }); + + app.message('me', async ({ signin, userGraph, send }) => { + if (!await signin()) { + return; + } + const me = await userGraph.call(endpoints.me.get); + await send(JSON.stringify(me)); + }); + ``` + + @@ -404,30 +404,30 @@ First, let's configure the `App` class in Teams JS. This is equivalent to Slack import { App } from '@microsoft/teams.apps'; const app = new App({ - // ... rest of App config - oauth: { - // The key here should match the OAuth Connection setting - // defined in your Azure Bot resource. - defaultConnectionName: 'custom', - }, + // ... rest of App config + oauth: { + // The key here should match the OAuth Connection setting + // defined in your Azure Bot resource. + defaultConnectionName: 'custom', + }, }); app.message('me', async ({ activity, signin, token, send }) => { - // In production, it is probably better to implement a local cache. - // (e.g. \`activity.from.id\` <-> token). - // Otherwise this triggers an API call to Azure Token Service on every inbound message. - if (!await signin()) { - return; - } - - // Call external API - const response = await fetch('https://example.com/api/helloworld', { - method: 'POST', - headers: { - "Authorization": token, - }, - }); - const result = await response.json(); - await send(JSON.stringify(result)); + // In production, it is probably better to implement a local cache. + // (e.g. \`activity.from.id\` <-> token). + // Otherwise this triggers an API call to Azure Token Service on every inbound message. + if (!await signin()) { + return; + } + + // Call external API + const response = await fetch('https://example.com/api/helloworld', { + method: 'POST', + headers: { + "Authorization": token, + }, + }); + const result = await response.json(); + await send(JSON.stringify(result)); }); -```` +```