<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: John Reilly</title>
    <description>The latest articles on Forem by John Reilly (@johnnyreilly).</description>
    <link>https://forem.com/johnnyreilly</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F844594%2Ff530b48c-6eff-49fc-a614-9945cf0f1da9.jpeg</url>
      <title>Forem: John Reilly</title>
      <link>https://forem.com/johnnyreilly</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/johnnyreilly"/>
    <language>en</language>
    <item>
      <title>Azure Open AI: handling capacity and quota limits with Bicep</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Thu, 17 Aug 2023 08:28:26 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/azure-open-ai-handling-capacity-and-quota-limits-with-bicep-4n3k</link>
      <guid>https://forem.com/johnnyreilly/azure-open-ai-handling-capacity-and-quota-limits-with-bicep-4n3k</guid>
      <description>&lt;p&gt;We're currently in the gold rush period of AI. The world cannot get enough. A consequence of this, is that rationing is in force. It's like the end of the second world war, but with GPUs. This is a good thing, because it means that we can't just spin up as many resources as we like. It's a bad thing, for the exact same reason.&lt;/p&gt;

&lt;p&gt;If you're making use of Azure's Open AI resources for your AI needs, you'll be aware that there are &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/quota?tabs=bicep"&gt;limits known as "quotas"&lt;/a&gt; in place. If you're looking to control how many resources you're using, you'll want to be able to control the capacity of your deployments. This is possible with Bicep.&lt;/p&gt;

&lt;p&gt;This post grew out of a &lt;a href="https://github.com/Azure/bicep-types-az/issues/1660#issuecomment-1643484703"&gt;GitHub issue&lt;/a&gt; around the topic where people were bumping on the message &lt;code&gt;the capacity should be null for standard deployment&lt;/code&gt; as they attempted to deploy. At the time that issue was raised, there was very little documentation on how to handle this. Since then, things have improved, but I thought it would be useful to have a post on the topic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ybrUYA3r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-08-17-azure-open-ai-capacity-quota-bicep/title-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ybrUYA3r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-08-17-azure-open-ai-capacity-quota-bicep/title-image.png" alt='title image reading "Azure Open AI: handling capacity and quota limits with Bicep" with the Azure Open AI / Bicep logos' width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing capacity and quota limits in the Azure Open AI Studio
&lt;/h2&gt;

&lt;p&gt;If you take a look at the &lt;a href="https://oai.azure.com/"&gt;Azure Open AI Studio&lt;/a&gt; you'll notice a "Quotas" section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5qmX0ahe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-08-17-azure-open-ai-capacity-quota-bicep/./screenshot-azure-ai-studio.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5qmX0ahe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-08-17-azure-open-ai-capacity-quota-bicep/./screenshot-azure-ai-studio.webp" alt="screenshot of azure open ai studio with quotas highlighted" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see above that we've got two deployments of GPT-35-Turbo in our subscription. Both of these contribute towards an overall limit of 360K TPM. If we try and deploy resources and have an overall capacity total that exceeds that, our deployment will fail.&lt;/p&gt;

&lt;p&gt;That being the case, we need to be able to control the capacity of our deployments. This is possible with Bicep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling capacity and quota limits with Bicep
&lt;/h2&gt;

&lt;p&gt;Consider the following &lt;code&gt;account-deployments.bicep&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@description('Name of the Cognitive Services resource')
param cognitiveServicesName string

@description('Name of the deployment resource.')
param deploymentName string

@description('Deployment model format.')
param format string

@description('Deployment model name.')
param name string

@description('Deployment model version.')
param version string = '1'

@description('The name of RAI policy.')
param raiPolicyName string = 'Default'

@allowed([
  'NoAutoUpgrade'
  'OnceCurrentVersionExpired'
  'OnceNewDefaultVersionAvailable'
])
@description('Deployment model version upgrade option. see https://learn.microsoft.com/en-us/azure/templates/microsoft.cognitiveservices/2023-05-01/accounts/deployments?pivots=deployment-language-bicep#deploymentproperties')
param versionUpgradeOption string = 'OnceNewDefaultVersionAvailable'

@description('''Deployments SKU see: https://learn.microsoft.com/en-us/azure/templates/microsoft.cognitiveservices/2023-05-01/accounts/deployments?pivots=deployment-language-bicep#sku
eg:

sku: {
  name: 'Standard'
  capacity: 10
}

''')
param sku object

// https://learn.microsoft.com/en-us/azure/templates/microsoft.cognitiveservices/2023-05-01/accounts?pivots=deployment-language-bicep
resource cog 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = {
  name: cognitiveServicesName
}

// https://learn.microsoft.com/en-us/azure/templates/microsoft.cognitiveservices/2023-05-01/accounts/deployments?pivots=deployment-language-bicep
resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = {
  name: deploymentName
  parent: cog
  sku: sku
  properties: {
    model: {
      format: format
      name: name
      version: version
    }
    raiPolicyName: raiPolicyName
    versionUpgradeOption: versionUpgradeOption
  }
}

output deploymentName string = deployment.name
output deploymentResourceId string = deployment.id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use this to deploy.... deployments (naming here is definitely confusing) to Azure like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var cognitiveServicesDeployments = [
  {
    name: 'OpenAi-gpt-35-turbo'
    shortName: 'gpt35t'
    model: {
      format: 'OpenAI'
      name: 'gpt-35-turbo'
      version: '0301'
    }
    sku: {
      name: 'Standard'
      capacity: repositoryBranch == 'refs/heads/main' ? 100 : 10 // capacity in thousands of TPM
    }
  }
]

// Model Deployment - one at a time as parallel deployments are not supported
@batchSize(1)
module openAiAccountsDeployments35Turbo 'account-deployments.bicep' = [for deployment in cognitiveServicesDeployments: {
  name: '${deployment.shortName}-cog-accounts-deployments'
  params: {
    cognitiveServicesName: openAi.outputs.cognitiveServicesName
    deploymentName: deployment.name
    format: deployment.model.format
    name: deployment.model.name
    version: deployment.model.version
    sku: deployment.sku
  }
}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're currently only deploying a single account deployment in our array, but we do it this way as it's not unusual to deploy multiple deployments together. Notice the &lt;code&gt;sku&lt;/code&gt; portion above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    sku: {
      name: 'Standard'
      capacity: repositoryBranch == 'refs/heads/main' ? 100 : 10
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we provision a larger &lt;code&gt;capacity&lt;/code&gt; for our feature branch deployments than our &lt;code&gt;main&lt;/code&gt; branch deployments. This demonstrates our own usage, whereby we deploy a smaller capacity for our feature branches so that we can test things out, but then deploy a larger capacity for our main branch deployments.&lt;/p&gt;

&lt;p&gt;Significantly, we're controlling the capacity of our deployments. The way in which you choose to decide on the capacity of your deployments is up to you, but the above demonstrates how you can do it with Bicep and stick within your quota limits.&lt;/p&gt;

</description>
      <category>azureopenai</category>
      <category>bicep</category>
    </item>
    <item>
      <title>Azure Pipelines meet Vitest</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Sat, 05 Aug 2023 07:56:19 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/azure-pipelines-meet-vitest-51hk</link>
      <guid>https://forem.com/johnnyreilly/azure-pipelines-meet-vitest-51hk</guid>
      <description>&lt;p&gt;This post explains how to integrate the tremendous test runner &lt;a href="https://vitest.dev/" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt; with the continuous integration platform &lt;a href="https://azure.microsoft.com/en-gb/products/devops/pipelines/" rel="noopener noreferrer"&gt;Azure Pipelines&lt;/a&gt;. If you read &lt;a href="https://johnnyreilly.com/2020/12/30/azure-pipelines-meet-jest" rel="noopener noreferrer"&gt;the post on integrating with Jest&lt;/a&gt;, you'll recognise a lot of common ground with this. Once again we want:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tests run as part of our pipeline&lt;/li&gt;
&lt;li&gt;A failing test fails the build&lt;/li&gt;
&lt;li&gt;Test results reported in Azure Pipelines UI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-08-05-azure-pipelines-meet-vitest%2Ftitle-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-08-05-azure-pipelines-meet-vitest%2Ftitle-image.png" alt="title image reading "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post assumes we have a Vitest project set up and an Azure Pipeline in place. Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests run as part of our pipeline
&lt;/h2&gt;

&lt;p&gt;First of all, lets get the tests running. We'll crack open our &lt;code&gt;azure-pipelines.yml&lt;/code&gt; file and, in the appropriate place add the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run test:ci&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;test'&lt;/span&gt;
  &lt;span class="na"&gt;workingDirectory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/client-app&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The above will, when run, trigger a &lt;code&gt;npm run test:ci&lt;/code&gt; in the &lt;code&gt;src/client-app&lt;/code&gt; folder of the project (it's here where the app lives). What does &lt;code&gt;test:ci&lt;/code&gt; do? Well, it's a script in the &lt;code&gt;package.json&lt;/code&gt; that looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"test:ci"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest run --reporter=default --reporter=junit --outputFile=reports/junit.xml"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You'll note above we've got 2 scripts; &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;test:ci&lt;/code&gt;. The former is the default script that Vitest will run when you run &lt;code&gt;npm test&lt;/code&gt;. The latter is the script that we'll use in our pipeline. The difference between the two is that the &lt;code&gt;test:ci&lt;/code&gt; script will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Doesn't run in watch mode&lt;/li&gt;
&lt;li&gt;Fail the build if any tests fail&lt;/li&gt;
&lt;li&gt;Produce a JUnit XML report which details test results. This is the format that Azure Pipelines can use to ingest test results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The test results are written to &lt;code&gt;reports/junit.xml&lt;/code&gt; which is a path relative to the &lt;code&gt;src/client-app&lt;/code&gt; folder. Because you may test this locally, it's probably worth adding the &lt;code&gt;reports&lt;/code&gt; folder to your &lt;code&gt;.gitignore&lt;/code&gt; file to avoid it accidentally being committed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Report test results in Azure Pipelines UI
&lt;/h2&gt;

&lt;p&gt;Our tests are running, but we're not seeing the results in the Azure Pipelines UI. For that we need the &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/publish-test-results-v2" rel="noopener noreferrer"&gt;&lt;code&gt;PublishTestResults&lt;/code&gt; task&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We need to add a new step to our &lt;code&gt;azure-pipelines.yml&lt;/code&gt; file &lt;em&gt;after&lt;/em&gt; our &lt;code&gt;npm run test:ci&lt;/code&gt; step:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishTestResults@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;supply&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pipelines'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;succeededOrFailed()&lt;/span&gt; &lt;span class="c1"&gt;# because otherwise we won't know what tests failed&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;testResultsFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;src/client-app/reports/junit.xml'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will read the test results from our &lt;code&gt;src/client-app/reports/junit.xml&lt;/code&gt; file and pump them into Pipelines. Do note that we're &lt;em&gt;always&lt;/em&gt; running this step; so if the previous step failed (as it would in the case of a failing test) we still pump out the details of what that failure was.&lt;/p&gt;

&lt;p&gt;And that's it! Azure Pipelines and Jest integrated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-08-05-azure-pipelines-meet-vitest%2Ftest-results.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-08-05-azure-pipelines-meet-vitest%2Ftest-results.webp" alt="screenshot of test results published to Azure Pipelines"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;The complete &lt;code&gt;azure-pipelines.yml&lt;/code&gt; additions look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run test:ci&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;test'&lt;/span&gt;
  &lt;span class="na"&gt;workingDirectory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/client-app&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishTestResults@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;supply&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pipelines'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;succeededOrFailed()&lt;/span&gt; &lt;span class="c1"&gt;# because otherwise we won't know what tests failed&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;testResultsFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;src/client-app/reports/junit.xml'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Please note, there's nothing special about the &lt;code&gt;reports/junit.xml&lt;/code&gt; file. You can change the name of the file and/or the location of the file. Just make sure you update the &lt;code&gt;testResultsFiles&lt;/code&gt; value in the &lt;code&gt;PublishTestResults&lt;/code&gt; task to match.&lt;/p&gt;

</description>
      <category>azurepipelines</category>
      <category>vitest</category>
    </item>
    <item>
      <title>Azure Container Apps, Bicep, bring your own certificates and custom domains</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Thu, 20 Jul 2023 15:13:45 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/azure-container-apps-bicep-bring-your-own-certificates-and-custom-domains-h3d</link>
      <guid>https://forem.com/johnnyreilly/azure-container-apps-bicep-bring-your-own-certificates-and-custom-domains-h3d</guid>
      <description>&lt;p&gt;Azure Container Apps supports custom domains via certificates. If you're looking to make use of the managed certificates in Azure Container Apps using Bicep, then you might want to take a look at &lt;a href="https://johnnyreilly.com/2023/06/18/azure-container-apps-bicep-managed-certificates-custom-domains" rel="noopener noreferrer"&gt;this post on the topic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post will instead look at how we can use the "bring your own certificates" approach in Azure Container Apps using Bicep. Well, as much as that is possible; there appear to be limitations in what can be achieved with Bicep at the time of writing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-07-20-azure-container-apps-bicep-bring-your-own-certificates-custom-domains%2Ftitle-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-07-20-azure-container-apps-bicep-bring-your-own-certificates-custom-domains%2Ftitle-image.png" alt="title image reading "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post assumes you already have an Azure Container App in place and you want to bind a custom domain to it. If you don't have an Azure Container App in place, then you might want to take a look at &lt;a href="https://johnnyreilly.com/2021/12/27/azure-container-apps-build-and-deploy-with-bicep-and-github-actions" rel="noopener noreferrer"&gt;this post on the topic&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make a certificate
&lt;/h2&gt;

&lt;p&gt;So the first thing we need to do is make a certificate. This is pretty simple, and in my case amounts to the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:4096 &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 3650 &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-keyout&lt;/span&gt; poorclaresarundel.org.key &lt;span class="nt"&gt;-out&lt;/span&gt; poorclaresarundel.org.crt &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=poorclaresarundel.org"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-addext&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:poorclaresarundel.org,DNS:www.poorclaresarundel.org,IP:8.8.8.8"&lt;/span&gt;
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +r poorclaresarundel.org.key
&lt;span class="nb"&gt;cat &lt;/span&gt;poorclaresarundel.org.crt poorclaresarundel.org.key &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; poorclaresarundel.org.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes a certificate with a 10 year expiry date. You'll note the &lt;code&gt;8.8.8.8&lt;/code&gt; listed as the IP address above. You should replace that with the static IP address of your Container Apps Environment. You can find that in the Azure Portal. It's listed on the "Overview" tab of your Azure Container App. It's the "Static IP" value:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-07-20-azure-container-apps-bicep-bring-your-own-certificates-custom-domains%2Fscreenshot-azure-portal-static-ip-address.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-07-20-azure-container-apps-bicep-bring-your-own-certificates-custom-domains%2Fscreenshot-azure-portal-static-ip-address.webp" alt="screenshot of the Azure Portal with the static IP address highlighted"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll also note the &lt;code&gt;poorclaresarundel.org&lt;/code&gt; listed as the domain name above. You should replace that with your domain name. You'll need to do that in two places; in the &lt;code&gt;openssl&lt;/code&gt; command and in the &lt;code&gt;subjectAltName&lt;/code&gt; value. More on the significance of that particular domain name later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Container apps environment and certificates
&lt;/h2&gt;

&lt;p&gt;Now, we'll crack open our Azure Container App's environment in the Azure Portal and navigate to the "Certificates" tab. Then we need to click on the "Bring your own certificates (.pfx)" tab, and we are presented with a screen like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-07-20-azure-container-apps-bicep-bring-your-own-certificates-custom-domains%2Fscreenshot-azure-portal-bring-your-own-certificates.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-07-20-azure-container-apps-bicep-bring-your-own-certificates-custom-domains%2Fscreenshot-azure-portal-bring-your-own-certificates.webp" alt="Screenshot of Azure Portal on the certificates screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the "Add certificate" button. Use that to upload your certificate - in my case that's the &lt;code&gt;poorclaresarundel.org.pem&lt;/code&gt; file. You'll need to provide the password for the certificate too. Oh and you'll be asked for a friendly name. We'll remember the friendly name you use - we'll need it later.&lt;/p&gt;

&lt;p&gt;This is a real world example; my aunt's website. My aunt is Poor Clare nun and, for years, I've done an average job of maintaining her &lt;a href="https://www.poorclaresarundel.org/" rel="noopener noreferrer"&gt;convent's website&lt;/a&gt;. If I was her, I'd be wishing her nephew was a designer rather than an engineer. Or maybe an engineer with more of a sense what looks good. But here we are - she's a nun and so consequently much too nice to say that. Anyway, I digress. The point is, I've got a certificate for her website and I'm going to use it here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-07-20-azure-container-apps-bicep-bring-your-own-certificates-custom-domains%2Fscreenshot-azure-portal-bring-your-own-certificates-uploaded.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjohnnyreilly%2Fblog.johnnyreilly.com%2Fmain%2Fblog-website%2Fblog%2F2023-07-20-azure-container-apps-bicep-bring-your-own-certificates-custom-domains%2Fscreenshot-azure-portal-bring-your-own-certificates-uploaded.webp" alt="screenshot of uploaded certificate in the Azure Portal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bicep for the custom domain
&lt;/h2&gt;

&lt;p&gt;I haven't managed to work out how one would handle the certificate upload in Bicep (and I rather suspect it is not supported). However, I have worked out how to handle the certificate in the Azure Container App once it's been uploaded with regards to custom domains. Find the &lt;code&gt;Microsoft.App/containerApps&lt;/code&gt; resource in your Bicep and add a &lt;code&gt;customDomains&lt;/code&gt; property to it. It should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource environment 'Microsoft.App/managedEnvironments@2022-10-01' = {
  name: environmentName
  // ...
}

resource webServiceContainerApp 'Microsoft.App/containerApps@2022-10-01' = {
  name: webServiceContainerAppName
  tags: tags
  location: location
  properties: {
    // ...
    configuration: {
      // ...
      ingress: {
        // ...
        customDomains: [
          {
              name: 'www.poorclaresarundel.org'
              // note the friendly name of "poorclaresarundel.org" forms the last segment of the id below
              certificateId: '${environment.id}/certificates/poorclaresarundel.org'
              bindingType: 'SniEnabled'
          }
        ]
      }
    }
    // ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With the above post you should be able to deploy an Azure Container App with a custom domain and provide your own certificate, which is then referenced using Bicep. If you'd like to see the full Bicep file, then you can find it &lt;a href="https://github.com/johnnyreilly/poorclaresarundel-aca/blob/main/infra/main.bicep" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attributions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.flaticon.com/free-icons/certificate" title="certificate icons" rel="noopener noreferrer"&gt;Certificate icon in title image created by Freepik - Flaticon&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azurecontainerapps</category>
      <category>bicep</category>
    </item>
    <item>
      <title>TypeScript 5.1: declaring JSX element types</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Sun, 09 Jul 2023 08:53:37 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/typescript-51-declaring-jsx-element-types-24k</link>
      <guid>https://forem.com/johnnyreilly/typescript-51-declaring-jsx-element-types-24k</guid>
      <description>&lt;p&gt;A new feature arrives with TypeScript 5.1, &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-1-beta/#decoupled-type-checking-between-jsx-elements-and-jsx-tag-types"&gt;it is described as "Decoupled Type-Checking Between JSX Elements and JSX Tag Types"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's all about handing control of JSX type definitions to libraries. With this feature, libraries can control what types are used for JSX elements. Why does this matter? Great question! Until version 5.1, TypeScript did an imperfect job of representing what is possible with JSX. This feature allows libraries to do a better job of that, and we'll look into it in this post.&lt;/p&gt;

&lt;p&gt;It's probably worth saying, that this is a complicated feature. If you don't understand it (and as the author of this post I'll confess that I had to work quite hard to understand it), &lt;strong&gt;that is okay&lt;/strong&gt;. This is a low level feature that is only likely to be used by library / type definition authors. It's a primitive that will unlock possibilites for people writing JSX - but it's something that people will mainly feel the benefit of, without directly doing anything themselves, or necessarily noticing that things have changed for the better.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u_RtNLbC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-07-09-typescript-5-1-declaring-jsx-element-types/title-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u_RtNLbC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-07-09-typescript-5-1-declaring-jsx-element-types/title-image.png" alt='title image reading "TypeScript 5.1: declaring JSX element types" with the TypeScript logo' width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the problem?
&lt;/h2&gt;

&lt;p&gt;TypeScript creates a type system which sits on top of JavaScript, and provides static typing capabilities. As the language has grown more sophisticated, it has been able to get closer and closer to representing the full range of possibilities that JavaScript offers. As an example of this evolution, if you remember the early days of TypeScript, you'll remember a time before union types. Back then, you had to use &lt;code&gt;any&lt;/code&gt; to represent a value that could be one of a number of types. That imperfect representation of JavaScript was solved with union types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-function printStringOrNumber(stringOrNumber: any) {
&lt;/span&gt;&lt;span class="gi"&gt;+function printStringOrNumber(stringOrNumber: string | number) {
&lt;/span&gt;    console.log(stringOrNumber);
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem we're looking at here is in the same vein. But it specifically applies to JSX; which is widely used in libraries like React. With JSX support in TypeScript (up to and including 5.0), it was not possible to accurately represent all JSX possibilities. This is because the type of a JSX element returned from a function component was always &lt;code&gt;JSX.Element | null&lt;/code&gt;. This is a type that is defined in the TypeScript compiler, and is not something that can be changed by a library author.&lt;/p&gt;

&lt;p&gt;How does this play out? Well, let's take a look at a simple example. Let's say we have a function component that returns a number. We might write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;ComponentThatReturnsANumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ComponentThatReturnsANumber&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is legitimate JSX, but it is not legitimate TypeScript. The TypeScript compiler will complain:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cw6wdG21--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-07-09-typescript-5-1-declaring-jsx-element-types/screenshot-typescript-playground.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cw6wdG21--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-07-09-typescript-5-1-declaring-jsx-element-types/screenshot-typescript-playground.png" alt="screenshot of typescript playground saying 'ComponentThatReturnsANumber' cannot be used as a JSX component. Its return type 'number' is not a valid JSX element.(2786)" width="690" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgIilQ3wG4AoczAVwDsNgJa4BhXSWpWmAFQAsUMZDGpRaAZwCCAOWogARkigAKAJRwA3uThwiIsXAAsAJgoBfSgB424Jl14ChSfRJlzFUOAHoAfOSA"&gt;You can see this in the TypeScript Playground&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This errors because function components that return anything but &lt;code&gt;JSX.Element | null&lt;/code&gt; are not allowed as element types in React according to TypeScript. However, in React, function components &lt;strong&gt;can&lt;/strong&gt; return a &lt;code&gt;ReactNode&lt;/code&gt;. That type includes &lt;code&gt;number | string | Iterable&amp;lt;ReactNode&amp;gt; | undefined&lt;/code&gt; (&lt;a href="https://github.com/reactjs/rfcs/pull/229"&gt;and will likely include &lt;code&gt;Promise&amp;lt;ReactNode&amp;gt;&lt;/code&gt; in the future&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;As an aside, a return value of &lt;code&gt;number&lt;/code&gt; would be perfectly fine in class components - the restrictions are different there. I spoke to Sebastian about this and he said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An interesting note is that before function components we did have full control. Due to &lt;code&gt;ElementClass&lt;/code&gt;, class components already could return &lt;code&gt;ReactNode&lt;/code&gt; at the type level. It was just function components that were missing full control (or any other component types Suspense or Profiler).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So this is the problem: is not possible to represent in TypeScript today what is actually possible in React (or other JSX libraries). Furthermore, what's returned from JSX may change over time, and TypeScript needs to be able to represent that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The arrival of &lt;code&gt;JSX.ElementType&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sebastian Silbermann &lt;a href="https://github.com/microsoft/TypeScript/pull/51328"&gt;opened a pull request to TypeScript&lt;/a&gt;. It had the title "RFC: Consult new JSX.ElementType for valid JSX element types". In that PR Sebastian explained the issue we've just looked at above, and proposed a solution. The solution was to introduce a new type, &lt;code&gt;JSX.ElementType&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To illustrate the what &lt;code&gt;JSX.ElementType&lt;/code&gt; actually is as compared to a JSX element, consider this illustration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// &amp;lt;Component /&amp;gt;
//  ^^^^^^^^^    JSX element type
// ^^^^^^^^^^^^^ JSX element
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The significance of &lt;code&gt;JSX.ElementType&lt;/code&gt; is that it is used to represent the type of a JSX element and &lt;strong&gt;allow library authors to control what types are used for JSX elements&lt;/strong&gt;. This control was not available before.&lt;/p&gt;

&lt;p&gt;The TypeScript pull request was merged, and so Sebastian (who helps maintain the React type definitions) exercised the new powers in &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped/pull/65135"&gt;this pull request to the DefinitelyTyped repository for the React type definitions&lt;/a&gt;. At the time of writing, this pull request is still open, but once merged and shipped the React community will feel the benefits.&lt;/p&gt;

&lt;p&gt;The changes are subtle; You can see in this pull request that &lt;code&gt;ReactElement | null&lt;/code&gt; is generally replaced with &lt;code&gt;ReactNode&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;     type JSXElementConstructor&amp;lt;P&amp;gt; =
&lt;span class="gd"&gt;-        | ((props: P) =&amp;gt; ReactElement&amp;lt;any, any&amp;gt; | null)
&lt;/span&gt;&lt;span class="gi"&gt;+        | ((props: P) =&amp;gt; ReactNode)
&lt;/span&gt;         | (new (props: P) =&amp;gt; Component&amp;lt;any, any&amp;gt;);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember how we mentioned earlier on that function components couldn't return numbers? Let's look at the updated tests in the PR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;    const ReturnNumber = () =&amp;gt; 0xeac1;
&lt;span class="gi"&gt;+   const FCNumber: React.FC = ReturnNumber;
&lt;/span&gt;    class RenderNumber extends React.Component {
        render() {
          return 0xeac1;
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this change, React components that return numbers are now valid JSX elements. This is because &lt;code&gt;JSX.ElementType&lt;/code&gt; is now &lt;code&gt;ReactNode&lt;/code&gt;, which includes numbers. Essentially this represents that new things are possible as a consequence of this change. The library and type definition author now has more control over what is possible in JSX.&lt;/p&gt;

&lt;p&gt;To quote Sebastian again:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Now we have control over any potential component type.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's look again at our component that produces a number again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;ComponentThatReturnsANumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ComponentThatReturnsANumber&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Sebastian's changes, this becomes valid TypeScript. And as React, and other JSX libraries evolve, TypeScript compatibility can also.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The TL;DR of this post is "TypeScript will better allow for the modelling of JSX in TypeScript 5.1". I'm indebted to &lt;a href="https://github.com/eps1lon"&gt;Sebastian Silbermann&lt;/a&gt; and &lt;a href="https://github.com/DanielRosenwasser"&gt;Daniel Rosenwasser&lt;/a&gt; for their explanations of this feature. Thanks in particular to Sebastian for implementing this feature and for reviewing this post.&lt;/p&gt;

&lt;p&gt;I hope this post helps you understand the feature a little better.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.logrocket.com/declaring-jsx-types-typescript-5-1/"&gt;This post was originally published on LogRocket.&lt;/a&gt;&lt;/p&gt;



&lt;br&gt;&lt;br&gt;
    &lt;br&gt;

</description>
      <category>typescript</category>
      <category>jsx</category>
      <category>react</category>
    </item>
    <item>
      <title>Azure Container Apps, Bicep, managed certificates and custom domains</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Sat, 17 Jun 2023 20:16:49 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/azure-container-apps-bicep-managed-certificates-and-custom-domains-2a2o</link>
      <guid>https://forem.com/johnnyreilly/azure-container-apps-bicep-managed-certificates-and-custom-domains-2a2o</guid>
      <description>&lt;p&gt;Azure Container Apps support managed certificates and custom domains. However, deploying them with Bicep is not straightforward - although it is possible. It seems likely there's a bug in the implementation in Azure, but I'm not sure. Either way, it's possible to deploy managed certificates and custom domains using Bicep. You just need to know how.&lt;/p&gt;

&lt;p&gt;If, instead, you're looking to make use of the "bring your own certificates" approach in Azure Container Apps using Bicep, then you might want to take a look at &lt;a href="https://johnnyreilly.com/2023/07/20/azure-container-apps-bicep-bring-your-own-certificates-custom-domains"&gt;this post on the topic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I've facetiously subtitled this post "a three pipe(line) problem" because it took three Azure Pipelines to get it working. This is not Azure Pipelines specific though, it's just that I was using Azure Pipelines to deploy the Bicep. Really, this applies to any way of deploying Bicep. GitHub Actions, Azure CLI or whatever.&lt;/p&gt;

&lt;p&gt;If you're here because you've encountered the dread message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Creating managed certificate requires hostname '....' added as a custom hostname to a container app in environment 'caenv-appname-dev'&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then you're in the right place. I'm going to explain how to get past that error message and get your custom domain working with your Azure Container App whilst still using Bicep. It's going to get ugly. But it will work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qC5rWCCO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-06-18-azure-container-apps-bicep-managed-certificates-custom-domains/title-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qC5rWCCO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-06-18-azure-container-apps-bicep-managed-certificates-custom-domains/title-image.png" alt='title image reading "Azure Container Apps, Bicep, managed certificates and custom domains" with the Azure Container App logos' width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A three pipe(line) problem
&lt;/h2&gt;

&lt;p&gt;I spent much of the last week attempting to attach a custom domain to an Azure Container App. I was using Bicep to deploy the infrastructure and I was using Azure DevOps to deploy the Bicep.&lt;/p&gt;

&lt;p&gt;There wasn't any documentation I could find about this, and so I decided to try and work it out for myself. I was able to get it working, but it was a bit of a journey. I'm going to share the steps I took here in the hope that it helps someone else.&lt;/p&gt;

&lt;p&gt;I titled this section "A three pipe(line) problem" because it turned out it required three Azure Pipeline runs to deploy a custom domain with a managed certificate. That, and the fact that it seemed a great way to get a Sherlock Holmes pun into the mix. I feel justified; there was no small amount of detective work involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse engineering the Bicep from the Azure Portal
&lt;/h2&gt;

&lt;p&gt;I knew that to get a custom domain working with an Azure Container App I would need to do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a managed certificate on my managed environment&lt;/li&gt;
&lt;li&gt;Create a custom domain on my container app&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I fired up the Azure Portal and did those two things. Then I went to the export template option and downloaded the ARM template. I was hoping to see how the Azure Portal did it. Since my eyes bleed a little when I attempt to read ARM templates, I &lt;a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/decompile?tabs=azure-cli"&gt;decompiled the ARM template into the Bicep equivalent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It actually seemed relatively simple. First there was a &lt;a href="https://learn.microsoft.com/en-us/azure/templates/microsoft.app/2022-11-01-preview/managedenvironments/managedcertificates?pivots=deployment-language-bicep"&gt;&lt;code&gt;Microsoft.App/managedEnvironments/managedCertificates&lt;/code&gt;&lt;/a&gt; resource which created the managed certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource managedEnvironmentManagedCertificate 'Microsoft.App/managedEnvironments/managedCertificates@2022-11-01-preview' = {
  parent: managedEnvironment
  name: '${managedEnvironment.name}-certificate'
  location: location
  tags: tags
  properties: {
    subjectName: customDomainName
    domainControlValidation: 'CNAME'
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then there was the addition of a &lt;a href="https://learn.microsoft.com/en-us/azure/templates/microsoft.app/containerapps?pivots=deployment-language-bicep#customdomain"&gt;&lt;code&gt;customDomains&lt;/code&gt;&lt;/a&gt; property to the &lt;code&gt;Microsoft.App/containerApps&lt;/code&gt; resource which referenced the managed certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource containerApp 'Microsoft.App/containerApps@2022-11-01-preview' = {
  //...
  properties: {
    configuration: {
      //...
      ingress: {
        //...
        customDomains: [
          {
            name: managedEnvironmentManagedCertificate.properties.subjectName
            certificateId: managedEnvironmentManagedCertificate.id
            bindingType: 'SniEnabled'
          }
        ]
        //...
      }
      //...
    }
    //...
  }
  //...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looked simple enough. I added those two resources to my Bicep file and ran the pipeline. It failed. I got this error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Creating managed certificate requires hostname '....' added as a custom hostname to a container app in environment 'caenv-appname-dev'&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Googling that error message didn't lead me to &lt;a href="https://github.com/microsoft/azure-container-apps/issues/607"&gt;this issue&lt;/a&gt; on the Azure Container Apps GitHub repo. Other people were having similar issues. I was able to gather enough clues from that issue to get me to a working approach. I may be the first person in the world to have got this far... Wouldn't that be special?&lt;/p&gt;

&lt;h2&gt;
  
  
  The three pipe(line) solution
&lt;/h2&gt;

&lt;p&gt;So whilst the original approach I came up with looked like it should work, it did not. What succeeded was an approach where I ran one pipeline to deploy some Bicep, a second pipeline to deploy some tweaked Bicep, and a third pipeline to deploy some more tweaked Bicep. Then profit.&lt;/p&gt;

&lt;p&gt;Herewith the details of the three pipelines / three Bicep deployments:&lt;/p&gt;

&lt;h3&gt;
  
  
  Bicep template 1: Create a disabled custom domain
&lt;/h3&gt;

&lt;p&gt;Our first Bicep template is going to create a custom domain on our container app. However, we're going to set the &lt;code&gt;bindingType&lt;/code&gt; property to &lt;code&gt;Disabled&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource containerApp 'Microsoft.App/containerApps@2022-11-01-preview' = {
  //...
  properties: {
    configuration: {
      //...
      ingress: {
        //...
        customDomains: [
          {
            name: customDomainName
            bindingType: 'Disabled'
          }
        ]
        //...
      }
      //...
    }
    //...
  }
  //...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy the above template and you'll have a custom domain on your container app, but it won't be active. This will be graduated to an active custom domain in the third Bicep template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bicep template 2: Create the managed certificate
&lt;/h3&gt;

&lt;p&gt;Now we're going to create the managed certificate by adding the following resource to our Bicep template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource managedEnvironmentManagedCertificate 'Microsoft.App/managedEnvironments/managedCertificates@2022-11-01-preview' = {
  parent: managedEnvironment
  name: '${managedEnvironment.name}-certificate'
  location: location
  tags: tags
  properties: {
    subjectName: customDomainName
    domainControlValidation: 'CNAME'
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At present there's no relation between the managed certificate and the custom domain. We'll fix that in the third Bicep template. Deploy this template and you'll have a managed certificate. However, the deployment will likely fail with an following error of the following form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AuthorizationFailed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The client 'GUID-GOES-HERE' with object id 'GUID-GOES-HERE' does not have authorization to perform action 'Microsoft.App/locations/managedCertificateOperationStatuses/read' over scope '/subscriptions/SUBSCRIPTION-ID-GOES-HERE/providers/Microsoft.App/locations/West Europe/managedCertificateOperationStatuses/GUID-GOES-HERE' or the scope is invalid. If access was recently granted, please refresh your credentials."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't sweat it. You should have a managed certificate. That's what we need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bicep template 3: Create an active custom domain
&lt;/h3&gt;

&lt;p&gt;Ready for the big finish? We're going to take our Bicep template back to what we originally tried:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource managedEnvironmentManagedCertificate 'Microsoft.App/managedEnvironments/managedCertificates@2022-11-01-preview' = {
  parent: managedEnvironment
  name: '${managedEnvironment.name}-certificate'
  location: location
  tags: tags
  properties: {
    subjectName: customDomainName
    domainControlValidation: 'CNAME'
  }
}

resource containerApp 'Microsoft.App/containerApps@2022-11-01-preview' = {
  //...
  properties: {
    configuration: {
      //...
      ingress: {
        //...
        customDomains: [
          {
            name: managedEnvironmentManagedCertificate.properties.subjectName
            certificateId: managedEnvironmentManagedCertificate.id
            bindingType: 'SniEnabled'
          }
        ]
        //...
      }
      //...
    }
    //...
  }
  //...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the &lt;code&gt;customDomains&lt;/code&gt; property of the &lt;code&gt;Microsoft.App/containerApps&lt;/code&gt; resource now references the managed certificate and is &lt;code&gt;SniEnabled&lt;/code&gt;. Deploy this template and you'll have a working managed certificate and a working custom domain on your container app. Huzzah!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I write this post purely to help others who may be struggling with this. I'm assuming this is some kind of bug, and I'm hoping the Azure Container Apps team will fix it soon. I've raised a specific issue on the Azure Container Apps GitHub repo for this problem. You can find it &lt;a href="https://github.com/microsoft/azure-container-apps/issues/796"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attributions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.flaticon.com/free-icons/bad-habit" title="bad habit icons"&gt;Pipe icon in title image created by Freepik - Flaticon&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azurecontainerapps</category>
      <category>bicep</category>
    </item>
    <item>
      <title>Azure Container Apps, Easy Auth and .NET authentication</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Sun, 11 Jun 2023 08:17:20 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/azure-container-apps-easy-auth-and-net-authentication-35d9</link>
      <guid>https://forem.com/johnnyreilly/azure-container-apps-easy-auth-and-net-authentication-35d9</guid>
      <description>&lt;p&gt;Liquid syntax error: Unknown tag 'endraw'&lt;/p&gt;
</description>
      <category>azurecontainerapps</category>
      <category>easyauth</category>
      <category>aspnet</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Private Bicep registry authentication with AzureResourceManagerTemplateDeployment@3</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Sun, 04 Jun 2023 08:36:15 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/private-bicep-registry-authentication-with-azureresourcemanagertemplatedeployment3-1kke</link>
      <guid>https://forem.com/johnnyreilly/private-bicep-registry-authentication-with-azureresourcemanagertemplatedeployment3-1kke</guid>
      <description>&lt;p&gt;If you deploy Bicep templates to Azure in Azure DevOps, you'll likely use the dedicated Azure DevOps task; the catchily named &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-resource-manager-template-deployment-v3?view=azure-pipelines"&gt;&lt;code&gt;AzureResourceManagerTemplateDeployment@3&lt;/code&gt;&lt;/a&gt;. This task has had support for deploying Bicep since early 2022. But whilst vanilla Bicep is supported, there's a use case which isn't supported; private Bicep registries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1780tRvQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-06-02-private-bicep-registry-authentication-azureresourcemanagertemplatedeployment/title-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1780tRvQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-06-02-private-bicep-registry-authentication-azureresourcemanagertemplatedeployment/title-image.png" alt='title image reading "Private Bicep registry authentication with AzureResourceManagerTemplateDeployment@3" with the Bicep, Azure and Azure DevOps logos' width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  "Unable to restore the module... Status: 401 (Unauthorized)"
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/private-module-registry?tabs=azure-powershell"&gt;Private Bicep registries&lt;/a&gt; are a great way to share Bicep modules across your organisation. We use them in the organisation that I'm part of; it's a good a way to help us move faster and to share common security baselines.&lt;/p&gt;

&lt;p&gt;Alas it turns out that the &lt;code&gt;AzureResourceManagerTemplateDeployment@3&lt;/code&gt; task doesn't presently play well with private Bicep registries. This is because it's necessary to authenticate to a private registry before you can consume modules. And that's not supported by the &lt;code&gt;AzureResourceManagerTemplateDeployment@3&lt;/code&gt; task. What does that mean? Well take a look at the following Azure Pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureResourceManagerTemplateDeployment@3&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DeployInfra&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy infra&lt;/span&gt;
  &lt;span class="na"&gt;retryCountOnTaskFailure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;deploymentScope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Resource Group&lt;/span&gt;
    &lt;span class="na"&gt;azureResourceManagerConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ variables.serviceConnection }}&lt;/span&gt;
    &lt;span class="na"&gt;subscriptionId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(subscriptionId)&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Or Update Resource Group&lt;/span&gt;
    &lt;span class="na"&gt;resourceGroupName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(resourceGroupName)&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(location)&lt;/span&gt;
    &lt;span class="na"&gt;templateLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Linked artifact&lt;/span&gt;
    &lt;span class="na"&gt;csmFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;infra/main.bicep'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll note that it passes a Bicep file to the &lt;code&gt;csmFile&lt;/code&gt; property. This is the file that will be deployed. But what if that file references modules from a private registry? Well, you'll see an error like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p1_rfUjs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-06-02-private-bicep-registry-authentication-azureresourcemanagertemplatedeployment/screenshot-authentication-failure.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p1_rfUjs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-06-02-private-bicep-registry-authentication-azureresourcemanagertemplatedeployment/screenshot-authentication-failure.png" alt="screenshot of the failing pipeline including the text 'Error BCP192: Unable to restore the module with reference &amp;quot;br:icebox.azurecr.io/bicep/ice/providers/document-db/database-accounts:v1.3&amp;quot;: Service request failed.'" width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see from the &lt;code&gt;Status: 401 (Unauthorized)&lt;/code&gt;, we have an authentication problem; the task doesn't know how to authenticate to the private registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workaround
&lt;/h2&gt;

&lt;p&gt;You might think, "oh well that's it then, I can't use private Bicep registries with the &lt;code&gt;AzureResourceManagerTemplateDeployment@3&lt;/code&gt; task". But you'd be wrong. There's a workaround. Essentially, when the &lt;code&gt;AzureResourceManagerTemplateDeployment@3&lt;/code&gt; task runs, it attempts to restore the modules it needs as a first step to compiling the Bicep in to ARM. But it only does this if it needs to. If we perform the restore manually first, then the task won't need to do it again. That's the trick.&lt;/p&gt;

&lt;p&gt;Before the &lt;code&gt;AzureResourceManagerTemplateDeployment@3&lt;/code&gt; task runs, we can run a task to restore the modules. We can use the &lt;code&gt;AzureCLI@2&lt;/code&gt; task to do this. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureCLI@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bicep restore&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;azureSubscription&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service-connection-with-access-to-registry&lt;/span&gt;
    &lt;span class="na"&gt;scriptType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
    &lt;span class="na"&gt;scriptLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inlineScript&lt;/span&gt;
    &lt;span class="na"&gt;inlineScript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;bicep restore infra/main.bicep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;service-connection-with-access-to-registry&lt;/code&gt; is an Azure Resource Manager service connection using service principal authentication which has access to the private Bicep registry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pm06CWGy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-06-02-private-bicep-registry-authentication-azureresourcemanagertemplatedeployment/screenshot-service-connection.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pm06CWGy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-06-02-private-bicep-registry-authentication-azureresourcemanagertemplatedeployment/screenshot-service-connection.webp" alt="screenshot of service connection" width="640" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So if the above task runs &lt;em&gt;prior&lt;/em&gt; to the &lt;code&gt;AzureResourceManagerTemplateDeployment@3&lt;/code&gt; task, then the modules will be restored and the &lt;code&gt;AzureResourceManagerTemplateDeployment@3&lt;/code&gt; task will be able to compile the Bicep in to ARM and deploy it to Azure. This solves the problem; albeit at the cost of an extra task in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  In the box, in the future?
&lt;/h2&gt;

&lt;p&gt;It would be tremendous if authentication to private Bicep registries was supported by the &lt;code&gt;AzureResourceManagerTemplateDeployment@3&lt;/code&gt; task. I've raised a feature request for this &lt;a href="https://github.com/microsoft/azure-pipelines-tasks/issues/18426"&gt;here&lt;/a&gt;. If you'd like to see this too, please do add your voice to the issue.&lt;/p&gt;

</description>
      <category>bicep</category>
      <category>azuredevops</category>
    </item>
    <item>
      <title>Static Web Apps CLI and Node.js 18: could not connect to API</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Sun, 21 May 2023 08:50:06 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/static-web-apps-cli-and-nodejs-18-could-not-connect-to-api-515h</link>
      <guid>https://forem.com/johnnyreilly/static-web-apps-cli-and-nodejs-18-could-not-connect-to-api-515h</guid>
      <description>&lt;p&gt;I make use of Azure Static Web Apps a lot. I recently upgraded to Node.js 18 and found that the Static Web Apps CLI no longer worked when trying to run locally; the API would not connect when running &lt;code&gt;swa start&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[swa] ❌ Could not connect to "http://localhost:7071/". Is the server up and running?&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This post shares a workaround.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--65SQtGaE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-20-static-web-apps-cli-node-18-could-not-connect-to-api/title-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--65SQtGaE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-20-static-web-apps-cli-node-18-could-not-connect-to-api/title-image.png" alt='title image reading "Static Web Apps CLI and Node.js 18: could not connect to API" with the Static Web Apps CLI and Node.js logos' width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The issue
&lt;/h2&gt;

&lt;p&gt;My own setup is a Vite front end and a Function App back end. I have a &lt;code&gt;package.json&lt;/code&gt; in the folder of the front end app with the following scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"swa start http://localhost:5173 --run &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm run dev&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; --api-location ../FunctionApp"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could see both front end and back end starting up in the console, but inevitably the SWA CLI would report:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[swa] ❌ Could not connect to "http://localhost:7071/". Is the server up and running?&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It turned out this came down to the version of Node.js I was using. I was using Node.js 18. Or rather it was due to an issue with a dependency of the Static Web Apps CLI; the &lt;a href="https://github.com/jeffbski/wait-on"&gt;wait-on&lt;/a&gt; library which waits for endpoints to become available. &lt;a href="https://github.com/jeffbski/wait-on/issues/137"&gt;With Node.js 18 this is broken&lt;/a&gt;. It's not clear a fix is imminent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workaround
&lt;/h2&gt;

&lt;p&gt;Various workarounds are suggested in &lt;a href="https://github.com/Azure/static-web-apps-cli/issues/663"&gt;this GitHub issue&lt;/a&gt;. I shared my own there, and I'm sharing it here too. (Mostly for me, I'll lay money I need this again and again.)&lt;/p&gt;

&lt;p&gt;In the root of my project I installed &lt;a href="https://www.npmjs.com/package/concurrently"&gt;concurrently&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i concurrently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, again in the root of my project I added the following scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"concurrently -n &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;staticwebapp,functionapp&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; -c &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;bgBlue.bold,bgMagenta.bold&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm run debug:staticwebapp&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm run debug:functionapp&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"debug:staticwebapp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd src/StaticWebApp &amp;amp;&amp;amp; npm run debug"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"debug:functionapp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd src/FunctionApp &amp;amp;&amp;amp; func start"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening here is that I'm running the Static Web Apps CLI and the Function App CLI in separate processes, and running them concurrently when we run &lt;code&gt;npm run debug&lt;/code&gt;. You'll note that the &lt;code&gt;debug:staticwebapp&lt;/code&gt; script is running another &lt;code&gt;debug&lt;/code&gt; script with the Static Web Apps CLI in the &lt;code&gt;src/StaticWebApp&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"swa start http://localhost:5173 --run &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm run dev&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; --api-location http://127.0.0.1:7071"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--api-location&lt;/code&gt; flag is pointing to the endpoint the Function App is surfaced at. This is the key to the workaround.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This takes us back to having a setup that will work with Node.js 18. Hopefully this is only needed for a short while, but it's good to have a workaround in the meantime.&lt;/p&gt;

</description>
      <category>azurestaticwebapps</category>
      <category>node</category>
    </item>
    <item>
      <title>TypeScript 5: importsNotUsedAsValues replaced by ESLint consistent-type-imports</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Tue, 09 May 2023 14:55:00 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/typescript-5-importsnotusedasvalues-replaced-by-eslint-consistent-type-imports-2e14</link>
      <guid>https://forem.com/johnnyreilly/typescript-5-importsnotusedasvalues-replaced-by-eslint-consistent-type-imports-2e14</guid>
      <description>&lt;p&gt;I really like type imports that are unambiguous. For this reason, I've made use of the &lt;code&gt;"importsNotUsedAsValues": "error"&lt;/code&gt; option in &lt;code&gt;tsconfig.json&lt;/code&gt; for a while now. This option has been deprecated in TypeScript 5.0.0, and will be removed in TypeScript 5.5.0. This post will look at what you can do instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8UPg-ZBe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-09-typescript-5-importsnotusedasvalues-error-eslint-consistent-type-imports/title-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8UPg-ZBe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-09-typescript-5-importsnotusedasvalues-error-eslint-consistent-type-imports/title-image.png" alt='title image reading "TypeScript 5:  raw `importsNotUsedAsValues` endraw  replaced by ESLint  raw `consistent-type-imports` endraw " with the ESLint and TypeScript logo' width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;"importsNotUsedAsValues": "error"&lt;/code&gt; provided
&lt;/h2&gt;

&lt;p&gt;Prior to TypeScript 5.0, if you wanted to make your type imports explicit, you could use the &lt;code&gt;"importsNotUsedAsValues": "error"&lt;/code&gt; option in &lt;code&gt;tsconfig.json&lt;/code&gt;. This would mean that you would need to use &lt;code&gt;import type&lt;/code&gt; for type imports, and &lt;code&gt;import&lt;/code&gt; for value imports. Consider the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ResourceGraphClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@azure/arm-resourcegraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my code I was only using &lt;code&gt;ResourceGraphClient&lt;/code&gt; as a type, so I would need to change it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ResourceGraphClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@azure/arm-resourcegraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;or&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ResourceGraphClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@azure/arm-resourcegraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if I rebelled, the TypeScript compiler would complain:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;This import is never used as a value and must use 'import type' because 'importsNotUsedAsValues' is set to 'error'.ts(1371)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n30rP39M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-09-typescript-5-importsnotusedasvalues-error-eslint-consistent-type-imports/screenshot-importsnotusedasvalues-error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n30rP39M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-09-typescript-5-importsnotusedasvalues-error-eslint-consistent-type-imports/screenshot-importsnotusedasvalues-error.png" alt="screenshot of VS Code displaying the error message" width="800" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript 5 deprecates &lt;code&gt;importsNotUsedAsValues&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;However, when I upgraded to TypeScript 5, I started seeing the following error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Option 'importsNotUsedAsValues' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. Use 'verbatimModuleSyntax' instead.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EJ1yYbBe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-09-typescript-5-importsnotusedasvalues-error-eslint-consistent-type-imports/screenshot-importsnotusedasvalues-deprecated.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EJ1yYbBe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-09-typescript-5-importsnotusedasvalues-error-eslint-consistent-type-imports/screenshot-importsnotusedasvalues-deprecated.png" alt="screenshot of VS Code displaying the error message" width="800" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The error was the result of &lt;a href="https://github.com/microsoft/TypeScript/pull/52203"&gt;this pull request&lt;/a&gt;. The message made me think I just needed to migrate to &lt;code&gt;verbatimModuleSyntax&lt;/code&gt;, like so:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="tsconfig.json"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;   "importsNotUsedAsValues": "error",&lt;/li&gt;
&lt;li&gt;   "verbatimModuleSyntax": true,
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, when I did so, my terminal became a sea of errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;src/telemetry.ts:7:13 - error TS1286: ESM syntax is not allowed &lt;span class="k"&gt;in &lt;/span&gt;a CommonJS module when &lt;span class="s1"&gt;'verbatimModuleSyntax'&lt;/span&gt; is enabled.

import &lt;span class="k"&gt;*&lt;/span&gt; as task from &lt;span class="s1"&gt;'./task.json'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            ~~~~

src/telemetry.ts:9:1 - error TS1287: A top-level &lt;span class="s1"&gt;'export'&lt;/span&gt; modifier cannot be used on value declarations &lt;span class="k"&gt;in &lt;/span&gt;a CommonJS module when &lt;span class="s1"&gt;'verbatimModuleSyntax'&lt;/span&gt; is enabled.

&lt;span class="nb"&gt;export &lt;/span&gt;async &lt;span class="k"&gt;function &lt;/span&gt;sendTelemetry&lt;span class="o"&gt;({&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out that in &lt;code&gt;verbatimModuleSyntax&lt;/code&gt;, you can't write ESM syntax in files that will emit as CommonJS - which is exactly what my codebase is doing. &lt;a href="https://github.com/andrewbranch"&gt;Andrew Branch&lt;/a&gt;, who is part of the TypeScript team, sent me an explanation from a draft of some new TypeScript docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In TypeScript 5.0, a new compiler option called &lt;code&gt;verbatimModuleSyntax&lt;/code&gt; was introduced to help TypeScript authors know exactly how their &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;export&lt;/code&gt; statements will be emitted. When enabled, the flag requires imports and exports in input files to be written in the form that will undergo the least amount of transformation before emit. So if a file will be emitted as ESM, imports and exports must be written in ESM syntax; if a file will be emitted as CJS, it must be written in the CommonJS-inspired TypeScript syntax (&lt;code&gt;import fs = require("fs")&lt;/code&gt; and &lt;code&gt;export = {}&lt;/code&gt;). This setting is particularly recommended for Node projects that use mostly ESM, but have a select few CJS files. It is not recommended for projects that currently target CJS, but may want to target ESM in the future.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It further turns out that &lt;code&gt;importsNotUsedAsValues&lt;/code&gt; was never intended to be used in in the way that I did; effectively as a linting mechanism. Andrew &lt;a href="https://github.com/microsoft/TypeScript/pull/52203#issuecomment-1476574601"&gt;said this on the topic&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;importsNotUsedAsValues&lt;/code&gt; was made to serve the opposite purpose you (and basically everyone) were using it for. By default, TypeScript elides unneeded import statements from JS emit even without marking them as &lt;code&gt;type&lt;/code&gt; imports. &lt;code&gt;importsNotUsedAsValues&lt;/code&gt; was created as a way to escape that behavior, not (primarily) to make it more explicit. &lt;code&gt;verbatimModuleSyntax&lt;/code&gt; allows you to escape the elision behavior, and takes the explicitness of what your imports mean a step further by making you write CJS-style imports when emitting to CJS. So in my book, all the important cases that &lt;code&gt;importsNotUsedAsValues&lt;/code&gt; (and &lt;code&gt;preserveValueImports&lt;/code&gt;) covered, plus more, are covered by &lt;code&gt;verbatimModuleSyntax&lt;/code&gt;, which is way more explainable. It’s mostly a matter of reducing complexity for the sake of explanation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Until the world has finished migrating to ES Modules (which will be a while), I'm going to need to stick with CommonJS as my emit target, whilst still planning to write ES Module imports in my code. But I really like being explicit about my imports. So what can I do?&lt;/p&gt;

&lt;h2&gt;
  
  
  ESLint and &lt;code&gt;@typescript-eslint/consistent-type-imports&lt;/code&gt; to the rescue
&lt;/h2&gt;

&lt;p&gt;I mentioned that I was using &lt;code&gt;importsNotUsedAsValues&lt;/code&gt; essentially as a linting mechanism. And it transpires that the answer to my need lives in ESLint itself. There's a rule named &lt;a href="https://typescript-eslint.io/rules/consistent-type-imports/"&gt;&lt;code&gt;@typescript-eslint/consistent-type-imports&lt;/code&gt;&lt;/a&gt; which tackles exactly this. If you're using &lt;a href="https://eslint.org/"&gt;ESLint&lt;/a&gt; and &lt;a href="https://typescript-eslint.io/"&gt;typescript-eslint&lt;/a&gt;, you can add this rule to your &lt;code&gt;.eslintrc.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```js title="eslintrc.js"&lt;br&gt;
module.exports = {&lt;br&gt;
  // ...&lt;br&gt;
  rules: {&lt;br&gt;
    // ...&lt;br&gt;
    '@typescript-eslint/consistent-type-imports': 'error', // the replacement of "importsNotUsedAsValues": "error"&lt;br&gt;
  },&lt;br&gt;
};&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Or if you prefer to have the type imports inline, you can use:



```js title="eslintrc.js"
module.exports = {
  // ...
  rules: {
    // ...
    '@typescript-eslint/consistent-type-imports': [
      'error',
      {
        fixStyle: 'inline-type-imports',
      },
    ], // the replacement of "importsNotUsedAsValues": "error"
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With this in place, we're back to where we were before; just with a different engine:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All imports in the declaration are only used as types. Use &lt;code&gt;import type&lt;/code&gt;.eslint@typescript-eslint/consistent-type-imports&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pn-VXGll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-09-typescript-5-importsnotusedasvalues-error-eslint-consistent-type-imports/screenshot-consistent-type-imports-error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pn-VXGll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-09-typescript-5-importsnotusedasvalues-error-eslint-consistent-type-imports/screenshot-consistent-type-imports-error.png" alt="screenshot of VS Code displaying the error message" width="800" height="68"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we even have the ability to auto-fix the errors as well now. Thanks &lt;code&gt;typescript-eslint&lt;/code&gt;!&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;no-import-type-side-effects&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;We are not quite done. There's another typescript-eslint rule that we can use to help us. &lt;a href="https://typescript-eslint.io/rules/no-import-type-side-effects/"&gt;&lt;code&gt;no-import-type-side-effects&lt;/code&gt;&lt;/a&gt; is a rule that will warn you if you have any side effects in your type imports. What does that mean? Well, consider the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// is transpiled to&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// which is the same as&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may not want a runtime import at all. You can do that by using a &lt;strong&gt;top-level&lt;/strong&gt; &lt;code&gt;type&lt;/code&gt; qualifier for imports when it only imports specifiers with an inline &lt;code&gt;type&lt;/code&gt; qualifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// is transpiled to.... nothing! Hence no side effects&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if side effects is something you're concerned about, consider this rule as well. Note that whether &lt;code&gt;import { type A } from 'mod'&lt;/code&gt; transpiles to a side-effect import or gets completely removed depends on your &lt;code&gt;tsc&lt;/code&gt; options, or what transpiler you’re using. But &lt;code&gt;import type&lt;/code&gt; statements &lt;em&gt;always&lt;/em&gt; get removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Thanks to Andrew Branch for reviewing this post, and massively improving it! Any mistakes are mine, not his.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>eslint</category>
    </item>
    <item>
      <title>Migrating Azure Functions from JSDoc JavaScript to TypeScript</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Mon, 08 May 2023 06:15:30 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/migrating-azure-functions-from-jsdoc-javascript-to-typescript-43ep</link>
      <guid>https://forem.com/johnnyreilly/migrating-azure-functions-from-jsdoc-javascript-to-typescript-43ep</guid>
      <description>&lt;p&gt;I wrote previously about how to implement a &lt;a href="https://johnnyreilly.com/2022/12/22/azure-static-web-apps-dynamic-redirects-azure-functions"&gt;dynamic redirect mechanism for Azure Static Web Apps using Azure Functions&lt;/a&gt;. I implemented this using JSDoc JavaScript. I've since migrated this to TypeScript and I thought it would be interesting to share the process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eq0RK1RT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-08-migrating-azure-functions-from-jsdoc-javascript-to-typescript/title-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eq0RK1RT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-08-migrating-azure-functions-from-jsdoc-javascript-to-typescript/title-image.png" alt='title image reading "Migrating Azure Functions from JSDoc JavaScript to TypeScript" with the JS and TS logos and Azure Functions logo' width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why migrate from JSDoc JavaScript to TypeScript?
&lt;/h2&gt;

&lt;p&gt;As regular readers will know, I'm both a big fan of TypeScript and a big fan of JSDoc JavaScript. I think both are great. So why migrate from JSDoc JavaScript to TypeScript? For me it's mostly about the developer experience; JSDoc is more verbose, and the wider ecosystem does a lot more TypeScript than it does JSDoc JavaScript. So when you get beyond a simple application, for me at least, TypeScript is a better choice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://johnnyreilly.com/"&gt;My blog&lt;/a&gt; is an &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/overview"&gt;Azure Static Web App&lt;/a&gt; with an Azure Functions back end. I've been using JSDoc JavaScript for my Azure Functions. This post will take the back end and migrate it to TypeScript. There's various affordances that I have in place already that I don't want to lose along the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I can debug in VS Code&lt;/li&gt;
&lt;li&gt;I deploy using GitHub Actions&lt;/li&gt;
&lt;li&gt;I have automated tests in place using Jest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these affordances are available to me with TypeScript, and I want to keep them. Let's begin migrating. Incidentally, the code for this migration &lt;a href="https://github.com/johnnyreilly/blog.johnnyreilly.com/pull/558"&gt;lies in this PR&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating the &lt;code&gt;tsconfig.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;tsconfig.json&lt;/code&gt; manages the way the TypeScript compiler interacts with our code. We'll start by migrating this:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="tsconfig.json"&lt;br&gt;
{&lt;br&gt;
  "compilerOptions": {&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;   "noEmit": true,&lt;/li&gt;
&lt;li&gt;   "noEmit": false,&lt;/li&gt;
&lt;li&gt;   "outDir": "dist",&lt;/li&gt;
&lt;li&gt;   "rootDir": ".",&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"sourceMap": true,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"allowJs": false,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"checkJs": false,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;"moduleResolution": "node",&lt;br&gt;
}&lt;br&gt;
}&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at the changes we've made:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're now emitting files from our compilation thanks to &lt;code&gt;noEmit&lt;/code&gt; being set to &lt;code&gt;false&lt;/code&gt;. We will no longer be actually running the code we write, but we will run the JavaScript we emit.&lt;/li&gt;
&lt;li&gt;We're specifying an &lt;code&gt;outDir&lt;/code&gt; of &lt;code&gt;dist&lt;/code&gt; - this is where our compiled JavaScript will be emitted.&lt;/li&gt;
&lt;li&gt;We're specifying a &lt;code&gt;rootDir&lt;/code&gt; of &lt;code&gt;.&lt;/code&gt; - this is the root of our TypeScript source files.&lt;/li&gt;
&lt;li&gt;We're creating source maps for our emitted JavaScript files - this will help us debug our original source code. (Even though we're not running it.)&lt;/li&gt;
&lt;li&gt;We're no longer allowing JavaScript files to be part of our program thanks to &lt;code&gt;allowJs&lt;/code&gt; being set to &lt;code&gt;false&lt;/code&gt;. (And we're not checking them either thanks to &lt;code&gt;checkJs&lt;/code&gt; being set to &lt;code&gt;false&lt;/code&gt; - I suspect this is implicitly set to &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;allowJs&lt;/code&gt; being &lt;code&gt;false&lt;/code&gt; - just to be clear I've specified it.)&lt;/li&gt;
&lt;li&gt;We're specifying a &lt;code&gt;moduleResolution&lt;/code&gt; of &lt;code&gt;node&lt;/code&gt; - this is how TypeScript will look up a file from a given module specifier.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Migrating the &lt;code&gt;package.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To migrate our &lt;code&gt;package.json&lt;/code&gt; we'll need to add some dependencies and tweak our scripts:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="package.json"&lt;br&gt;
  "scripts": {&lt;br&gt;
    "build": "tsc",&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;   "watch": "tsc -w",&lt;/li&gt;
&lt;li&gt;   "prestart": "npm run build",
"start": "func start",
"test": "jest"
},
"devDependencies": {
"@azure/functions": "^3.5.0",&lt;/li&gt;
&lt;li&gt;   "@types/jest": "^29.2.5",&lt;/li&gt;
&lt;li&gt;   "@types/node": "^18.x",
"jest": "^29.3.1",&lt;/li&gt;
&lt;li&gt;   "ts-jest": "^29.1.0",
"typescript": "^5.0.0"
}
}
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're adding two scripts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;watch&lt;/code&gt; - this will watch our TypeScript files and recompile them when they change - we don't need this really; but it can be useful depending on your preferred workflow.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prestart&lt;/code&gt; - this will run before &lt;code&gt;start&lt;/code&gt; and will ensure our TypeScript files are compiled, and we have up to date JavaScript to run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our &lt;code&gt;devDependencies&lt;/code&gt; we're adding dependencies for Jest and Node.js. We're also adding &lt;code&gt;ts-jest&lt;/code&gt; which will allow us to run Jest tests written in TypeScript.&lt;/p&gt;
&lt;h2&gt;
  
  
  Migrating &lt;code&gt;.vscode/settings.json&lt;/code&gt; and &lt;code&gt;.vscode/tasks.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I mentioned debugging earlier - to get that in place we need to work on the &lt;code&gt;settings.json&lt;/code&gt; and &lt;code&gt;tasks.json&lt;/code&gt; files in the &lt;code&gt;.vscode&lt;/code&gt; folder. First the &lt;code&gt;settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="settings.json"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; "azureFunctions.projectLanguage": "JavaScript",&lt;/li&gt;
&lt;li&gt; "azureFunctions.projectLanguage": "TypeScript",
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above is pretty self explanatory. We're changing the language of our Azure Functions project from JavaScript to TypeScript. Now the &lt;code&gt;tasks.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="tasks.json"&lt;br&gt;
{&lt;br&gt;
  "version": "2.0.0",&lt;br&gt;
  "tasks": [&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;   {&lt;/li&gt;
&lt;li&gt;     "type": "shell",&lt;/li&gt;
&lt;li&gt;     "label": "npm build (functions)",&lt;/li&gt;
&lt;li&gt;     "command": "npm run build",&lt;/li&gt;
&lt;li&gt;     "dependsOn": "npm install (functions)",&lt;/li&gt;
&lt;li&gt;     "problemMatcher": "$tsc",&lt;/li&gt;
&lt;li&gt;     "options": {&lt;/li&gt;
&lt;li&gt;       "cwd": "${workspaceFolder}/blog-website/api"&lt;/li&gt;
&lt;li&gt;     }&lt;/li&gt;
&lt;li&gt;   },
]
}
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We've got a new task in place here that runs our &lt;code&gt;npm run build&lt;/code&gt; script. This will compile our TypeScript files to JavaScript. We've also got a &lt;code&gt;cwd&lt;/code&gt; set to &lt;code&gt;blog-website/api&lt;/code&gt; - this is the folder where our Azure Functions live - your own will likely be different. Alongside this new script, there's some tweaks to existing tasks to make them depend on our new build task:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="tasks.json"&lt;br&gt;
{&lt;br&gt;
  "version": "2.0.0",&lt;br&gt;
  "tasks": [&lt;br&gt;
    {&lt;br&gt;
      "type": "func",&lt;br&gt;
      "label": "func: host start",&lt;br&gt;
      "command": "host start",&lt;br&gt;
      "problemMatcher": "$func-node-watch",&lt;br&gt;
      "isBackground": true,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;     "dependsOn": "npm build (functions)",&lt;/li&gt;
&lt;li&gt;     "options": {&lt;/li&gt;
&lt;li&gt;       "cwd": "${workspaceFolder}/blog-website/api"&lt;/li&gt;
&lt;li&gt;     }
},
// ...
{
  "type": "shell",
  "label": "npm prune (functions)",
  "command": "npm prune --production",&lt;/li&gt;
&lt;li&gt;     "dependsOn": "npm build (functions)",
  "problemMatcher": [],
  "options": {
    "cwd": "${workspaceFolder}/blog-website/api"
  }
}
]
}
```
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Migrate our Azure Functions
&lt;/h2&gt;

&lt;p&gt;This isn't going to be an exhaustive guide of migrating from JSDoc JavaScript to TypeScript. I'm going to focus on the things that I found most relevant to Azure Functions. Let's start with the &lt;code&gt;fallback/function.json&lt;/code&gt; file that powers our dynamic redirects and lives at &lt;code&gt;/api/fallback&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="fallback/function.json"&lt;br&gt;
{&lt;br&gt;
  "bindings": [&lt;br&gt;
    {&lt;br&gt;
      "authLevel": "anonymous",&lt;br&gt;
      "type": "httpTrigger",&lt;br&gt;
      "direction": "in",&lt;br&gt;
      "name": "req",&lt;br&gt;
      "methods": ["get", "post"]&lt;br&gt;
    },&lt;br&gt;
    {&lt;br&gt;
      "type": "http",&lt;br&gt;
      "direction": "out",&lt;br&gt;
      "name": "res"&lt;br&gt;
    }&lt;br&gt;
  ],&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; "scriptFile": "../dist/fallback/index.js"
}
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's one addition here, to repoint the &lt;code&gt;scriptFile&lt;/code&gt; to the compiled JavaScript.&lt;/p&gt;

&lt;p&gt;Now let's look at the code for the function. Originally a JavaScript file named &lt;code&gt;index.js&lt;/code&gt;; it must be renamed to &lt;code&gt;index.ts&lt;/code&gt;. The original code looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```js title="fallback/index.js"&lt;br&gt;
const redirect = require('./redirect');&lt;br&gt;
const saveToDatabase = require('./saveToDatabase');&lt;/p&gt;

&lt;p&gt;/**&lt;br&gt;
 *&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; { import("@azure/functions").Context } context&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; { import("@azure/functions").HttpRequest } req
*/
async function fallback(context, req) {
//...
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;module.exports = fallback;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


After renaming to `index.ts` and adding some TypeScript types, it looks like this:



```ts title="fallback/index.ts"
import type { AzureFunction, Context, HttpRequest } from '@azure/functions';

import { redirect } from './redirect';
import { saveToDatabase } from './saveToDatabase';

const httpTrigger: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise&amp;lt;void&amp;gt; {
  //...
};

export default httpTrigger;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As we can see, the type importing becomes much more succinct. We're also exporting the function as &lt;code&gt;default&lt;/code&gt; rather than &lt;code&gt;module.exports&lt;/code&gt;. This is because we're using ES Modules rather than CommonJS modules for authoring. We can also see that we're using &lt;code&gt;import&lt;/code&gt; rather than &lt;code&gt;require&lt;/code&gt; to import our functions. Whilst CommonJS was more straightforward to use, the syntax for ES Modules feels much nicer to use; at least to me. (It's worth noting that we're not using ES Modules in our compiled JavaScript - we're still using CommonJS there; but we don't need to think about that when we're writing our code.)&lt;/p&gt;

&lt;p&gt;I won't walk through migrating the other files, but the process is the same. Rename the file to &lt;code&gt;.ts&lt;/code&gt;, add TypeScript types, and use &lt;code&gt;import&lt;/code&gt; rather than &lt;code&gt;require&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Migrating our tests
&lt;/h2&gt;

&lt;p&gt;We're pretty much on the home stretch now; we just need to migrate our tests. Let's start with the &lt;code&gt;jest.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="jest.config.js"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; preset: 'ts-jest',&lt;/li&gt;
&lt;li&gt; testPathIgnorePatterns: ['/node_modules/', '/dist/'],
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're adding a &lt;code&gt;preset&lt;/code&gt; of &lt;code&gt;ts-jest&lt;/code&gt; which will allow us to run TypeScript tests. If you recall we added the &lt;code&gt;ts-jest&lt;/code&gt; dependency earlier; it was for this.&lt;/p&gt;

&lt;p&gt;We're also adding &lt;code&gt;dist&lt;/code&gt; to our &lt;code&gt;testPathIgnorePatterns&lt;/code&gt; - this is because we don't want to run our tests against our compiled JavaScript - we'd end up running the tests twice without this.&lt;/p&gt;

&lt;p&gt;Let's turn our attention to the tests directly. Again we do the classic rename from &lt;code&gt;.js&lt;/code&gt; to &lt;code&gt;.ts&lt;/code&gt;, and our imports become terser and more ES Module-y:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="redirect.test.ts"&lt;br&gt;
-/**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;* &lt;a class="mentioned-user" href="https://dev.to/typedef"&gt;@typedef&lt;/a&gt; { import("@azure/functions").Logger } Logger&lt;/li&gt;
&lt;li&gt;*/
+import type { Logger } from '@azure/functions';
-const { describe, expect, test } = require('@jest/globals');
+import { describe, expect, test } from '@jest/globals';
-const redirect = require('./redirect');
+import { redirect } from './redirect';
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beautiful. Finally we've got some tweaks to the code of the tests themselves. Firstly, declaring our mock becomes much easier:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff title="redirect.test.ts"&lt;br&gt;
-/** @type {jest.Mock} */ const mockLogger = jest.fn();&lt;br&gt;
+const mockLogger: jest.Mock = jest.fn();&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Secondly, casting our mock to the type we want is much more straightforward:



```diff title="redirect.test.ts"
-/** @type {any} */ (mockLogger)
+mockLogger as unknown as Logger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have no real how to &lt;code&gt;as&lt;/code&gt; cast twice in JSDoc - and now I don't need to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We now have a TypeScript Azure Functions codebase, with all of the debugging / testing / deployment affordances we had before. We didn't have to do anything around deployment, because it just works™️. I hope this post has been useful to you!&lt;/p&gt;

</description>
      <category>azurefunctions</category>
      <category>jsdoc</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Teams Direct Message API with Power Automate</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Thu, 04 May 2023 07:29:27 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/teams-direct-message-api-with-power-automate-3m3g</link>
      <guid>https://forem.com/johnnyreilly/teams-direct-message-api-with-power-automate-3m3g</guid>
      <description>&lt;p&gt;I've written previously about &lt;a href="https://johnnyreilly.com/2019/12/18/teams-notification-webhooks"&gt;sending Teams notifications using a webhook&lt;/a&gt;, and it's a technique I've used a lot. But I've always wanted to be able to send a direct message to a user, and that's not possible with the webhook approach. I work with a marvellous fellow named Chris Tacey-Green, and he's figured out a way to do this using Power Automate and the Teams Notification API. I'm going to describe how he did it here, with a little help from him.&lt;/p&gt;

&lt;p&gt;It's probably worth saying, both Chris and I work for Investec, and we're going to share some of the things we've learned about using Teams and Power Automate in the hope that it's useful to others. But we're not speaking on behalf of Investec, and we're not suggesting that this is the best way to do things. This is likely in the "do things that do not scale" category. Significantly though, it works!&lt;/p&gt;

&lt;p&gt;You will see some screenshots of our internal Teams environment, but we've tried to keep them to a minimum, and we're not going to share any sensitive information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--guP1JvZh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/title-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--guP1JvZh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/title-image.png" alt='title image reading "Teams Direct Message API with Power Automate" with a Teams logo' width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What are we trying to do?
&lt;/h2&gt;

&lt;p&gt;Teams is a great way to keep people informed of what's going on. But sometimes you want to send a message to a specific person. You can @mention them in a channel, but that's not the same as a direct message. And you can send them an email, but that's not the same as a direct message either. What we want is a way to send a direct message to a user in Teams, via an API. Because we want to automate. Tragically, sending DMs in Teams via an API is not supported at the time of writing (or if it is - please let us know!)&lt;/p&gt;

&lt;p&gt;All is not lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Power Automate
&lt;/h2&gt;

&lt;p&gt;Power Automate is a tool that allows you to automate tasks. It's a low-code/no-code tool that allows you to create workflows that can be triggered by events. It's a great tool for automating tasks. And it's a great tool for sending direct messages in Teams. Or so it turns out.&lt;/p&gt;

&lt;p&gt;You see, it's possible to send a notification to a user in Teams using the Teams Notification API. And it's possible to trigger a Power Automate workflow using the "when a new channel message is added" trigger. So if we can get a notification to the Teams Notification API, we can trigger a Power Automate workflow. And if we can trigger a Power Automate workflow, we can send a direct message to a user in Teams.&lt;/p&gt;

&lt;p&gt;This post will do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Describe how to set up a Power Automate workflow that sends a direct message to a user in Teams&lt;/li&gt;
&lt;li&gt;Describe how to trigger that workflow using the Teams Notification API and Adaptive Card messages&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting up the Power Automate workflow
&lt;/h2&gt;

&lt;p&gt;First things first, we need to create a Power Automate workflow that sends a direct message to a user in Teams. This is pretty straightforward, but when it came to doing this, I knew &lt;em&gt;nothing&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Before we start this, I should warn you there's going to be lots of screenshots. As far as I'm aware, there's not a code-first way to create Power Automate flows, so point and click is the only game in town.&lt;/p&gt;

&lt;p&gt;The first thing to do, is fire up Teams and install the Power Automate app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7bi7un9P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-app.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7bi7un9P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-app.webp" alt="screenshot of installing the Power Automate app" width="800" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows us to access the Power Automate app. There we can create a new workflow (from blank) with the "when a new channel message is added" trigger:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Tl21NwTp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-when-a-new-channel-message-is-added.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Tl21NwTp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-when-a-new-channel-message-is-added.webp" alt="screenshot of creating the Power Automate flow with the when a new channel message is added trigger" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We then need to select the team and channel that we want to trigger the workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5bPhFlU5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-team-and-channel.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5bPhFlU5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-team-and-channel.webp" alt="screenshot of selecting the team and channel" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll note in the screenshot above, we've got a dedicated channel for this workflow. This is because we don't want to disturb channels people are already using with the messages we will write to this channel. To all intents and purposes, this channel could actually be invisible to users - we just need it to exist to be our carrier pidgeon.&lt;/p&gt;

&lt;p&gt;With the trigger in place, we need to create the action. We're going to use the "apply to each" control, which will run for every message that comes through. We want to use the "Message mentions" output, which will give us the user that was mentioned in the message. We don't want to use the similarly named "Message mentions item":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VfvXahxK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-select-output-previous-steps.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VfvXahxK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-select-output-previous-steps.webp" alt="screenshot of selecting the output from previous steps" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The message mention gives us our user, we'll then use the "Get user profile (V2)" operation so we can look up that user up.&lt;/p&gt;

&lt;p&gt;It's at this point, that we start to do something slightly more complicated in the Power Automate UI. We're going to need to supply an id to the "Get user profile (V2)" operation. We're going to use an expression to determine this id. The expression we're going to use is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;triggerOutputs()?['body/mentions'][0].mentioned.user.id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This expression is operating on JSON similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@odata.type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#microsoft.graph.chatMessage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"etag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1683099494281"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messageType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdDateTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-05-03T07:38:14.281Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastModifiedDateTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-05-03T07:38:14.281Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mentions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mentionText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Reilly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mentioned"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"application"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"device"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"conversation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"@odata.type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#microsoft.graph.teamworkUserIdentity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-id-it-is-a-guid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Reilly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"userIdentityType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aadUser"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So that expression is just some JavaScript that gets us to the value we need; the id of the first mentioned user. We provide that in the "Expression" field and then click the "OK" button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aCutC66r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-expression1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aCutC66r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-expression1.webp" alt="screenshot of entering that into PA" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it's time for our final action - sending on the message with the "post card in chat or channel" operation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dnO6pwZ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-post-card.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dnO6pwZ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-post-card.webp" alt="screenshot of adding a post card entry" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll post as the Flow bot, post in a chat with the Flow bot and make our recipient "Mail" (which behind the scenes is the expression &lt;code&gt;outputs('Get_user_profile_(V2)')?['body/mail']&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Fz7OWvj8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-recipient-mail.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Fz7OWvj8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-recipient-mail.webp" alt="screenshot of adding the recipient mail" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, it's once more expression time, as we use this value for our Adaptive Card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;triggerOutputs()?['body/attachments'][0]['content']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oSFac5Bh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-expression2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oSFac5Bh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-expression2.webp" alt="screenshot of entering expression" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hopefully, what you've realised is that we're just taking the Adaptive Card from the message that triggered the workflow and passing it on to the recipient. We're intentionally doing as little as possible in the Power Automate workflow, as it's the trickiest part of our solution to work with. (Debugging Power Automate workflows is possible, but it's not the most fun you'll ever have.)&lt;/p&gt;

&lt;p&gt;We now have our complete workflow, and it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sCTRtrca--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-workflow.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sCTRtrca--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-workflow.webp" alt="screenshot of the Power Automate workflow" width="800" height="920"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Triggering the Power Automate workflow
&lt;/h2&gt;

&lt;p&gt;Whilst we have a workflow, we don't have anything to trigger it yet. To do that, we'll need to send a message to the channel we created earlier. We can do this using the Teams Notification API. And it needs to be a special kind of message; it needs to be an Adaptive Card, which includes a mention of the person we want to direct message.&lt;/p&gt;

&lt;p&gt;My post on &lt;a href="https://johnnyreilly.com/2019/12/18/teams-notification-webhooks"&gt;sending Teams notifications using a webhook&lt;/a&gt; describes how to send a message to a channel using the Teams Notification API. We're going to use the same technique, but we're going to send an &lt;a href="https://github.com/Microsoft/AdaptiveCards/"&gt;Adaptive Card&lt;/a&gt; instead of a plain message. I won't repeat the details of creating a webhook connector here, instead let's just focus on what we need to send to the API.&lt;/p&gt;

&lt;p&gt;Ultimately, it's just a &lt;a href="https://johnnyreilly.com/2023/03/09/node-18-axios-and-unsafe-legacy-renegotiation-disabled"&gt;POST request to the webhook connector&lt;/a&gt;, with a JSON body that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attachments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"contentType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.microsoft.card.adaptive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"contentUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AdaptiveCard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TextBlock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"medium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hey &amp;lt;at&amp;gt;John Reilly&amp;lt;/at&amp;gt;!&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;You have 102 unresolved secret findings! Click on the button below to view them."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"wrap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://adaptivecards.io/schemas/adaptive-card.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"msteams"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"entities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mention"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;at&amp;gt;John Reilly&amp;lt;/at&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"mentioned"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John.Reilly@investec.co.uk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Reilly"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Action.OpenUrl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"View findings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://some.url.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part that's relevant to us is the &lt;code&gt;msteams&lt;/code&gt; property. This is where we specify the user we want to mention. We do this by specifying the &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; properties. The &lt;code&gt;id&lt;/code&gt; property is the email address of the user we want to mention. The &lt;code&gt;name&lt;/code&gt; property is the display name of the user we want to mention.&lt;/p&gt;

&lt;p&gt;This is the secret sauce that allows our Power Automate flow to work. It reads these values and uses them to send a message to the user we want to mention. Alongside that, the &lt;code&gt;msteams.entities[].text&lt;/code&gt; property must be in your message body. That's what Teams uses to link the mention to the part of the message that's "doing the mentioning" (thanks Chris for pointing this out).&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it out
&lt;/h2&gt;

&lt;p&gt;So far, so screenshots and code. Does it work? Let's find out.&lt;/p&gt;

&lt;p&gt;When we run our tool for triggering the Teams Notification API, we get a message in our channel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j9-17JtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-adaptive-card-in-channel.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j9-17JtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-adaptive-card-in-channel.webp" alt="screenshot of the adaptive card in the shared teams channel" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that it has the @mention of the user: me. Now that this message in the relevant channel exists, our Power Automate workflow will be triggered. I've seen it take between 2 and 10 minutes for the trigger to fire. When it does, the flow runs and we get a direct message from the Flow bot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3-e2IbsW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-teams-direct-message.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3-e2IbsW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-teams-direct-message.webp" alt="screenshot of the adaptive card in a teams direct chat" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is our handrolled direct message to a user in Microsoft Teams. It's the same message we sent to the channel, just forwarded on by the Flow bot. Brilliant. The eagle eyed amongst you will note the ugly &lt;code&gt;&amp;lt;at id="0"&amp;gt;John Reilly&amp;lt;/at&amp;gt;&lt;/code&gt;; we're losing something in our forwarding. This could be remedied if we made our Power Automate flow a little more complex, but as mentioned, we're trying to keep our flow as simple as possible. The &lt;code&gt;&amp;lt;at id="0"&amp;gt;&lt;/code&gt; is a small price to pay for the simplicity of our flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  User blocked the conversation with the bot
&lt;/h2&gt;

&lt;p&gt;As you look at your Power Automate Flow runs, you can sometimes spot failures along these lines:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QKUKgQ9M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-flow-failure.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QKUKgQ9M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-power-automate-flow-failure.webp" alt="screenshot of a failed run of the power automate flow" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you look to the right on that screenshot you can see the error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Bot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;framework&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;error:&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ConversationBlockedByUser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"User blocked the conversation with the bot."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These kinds of failures appear to be a consequence of someone having blocked the Power Automate Flow bot. If you dig into the inputs ("Click to download" in the screenshot) you can discover the user who blocked the bot and have a conversation with them about it. Unblocking seems to be fairly straightforward; you just need to right-click / ctrl-click on the Power Automate app in Teams and select "Unblock":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3iAgXHri--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-unblock-the-bot.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3iAgXHri--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-05-04-ms-teams-direct-message-api/screenshot-unblock-the-bot.webp" alt="screenshot of unblocking the bot" width="552" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our experience, this is a rare occurrence, but it's worth being aware of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Together we've built a mechanism for sending a direct message to a user in Microsoft Teams. We've used the Teams Notification API to send a message to a channel, and we've used Power Automate to pick up that message and send it on to the user we want to mention.&lt;/p&gt;

&lt;p&gt;Thanks so much to Chris for coming up with this novel way of sending a direct message to a user in Microsoft Teams. I hope you've found this post useful.&lt;/p&gt;

</description>
      <category>teams</category>
      <category>powerautomate</category>
    </item>
    <item>
      <title>Docusaurus: Structured Data FAQs with MDX</title>
      <dc:creator>John Reilly</dc:creator>
      <pubDate>Sat, 08 Apr 2023 20:30:07 +0000</pubDate>
      <link>https://forem.com/johnnyreilly/docusaurus-structured-data-faqs-with-mdx-3jd9</link>
      <guid>https://forem.com/johnnyreilly/docusaurus-structured-data-faqs-with-mdx-3jd9</guid>
      <description>&lt;p&gt;import FAQStructuredData from "../../src/theme/MDXComponents/FAQStructuredData";&lt;/p&gt;

&lt;p&gt;export const faqs = [&lt;br&gt;
{&lt;br&gt;
question: "How do I use the FAQ Structured Data component?",&lt;br&gt;
answer:&lt;br&gt;
"Simply create an import statement for the component and then use it in your MDX file. You'll need to pass in an array of FAQs. This array can be inline, you can declare it as a variable, or you can import it from another file.",&lt;br&gt;
},&lt;br&gt;
{&lt;br&gt;
question: "How do I use the FAQ Structured Data component in a blog post?",&lt;br&gt;
answer:&lt;br&gt;
"The usage is the same as in a regular MDX file. But the import statement will sit after the frontmatter of the blog post.",&lt;br&gt;
},&lt;br&gt;
{&lt;br&gt;
question:&lt;br&gt;
"Can I use the FAQ Structured Data component in a regular MD file?",&lt;br&gt;
answer: "Yes! It just works™️.",&lt;br&gt;
},&lt;br&gt;
];&lt;/p&gt;

&lt;p&gt;I've written previously about &lt;a href="https://johnnyreilly.com/2021/10/15/structured-data-seo-and-react"&gt;using Structured Data with React&lt;/a&gt;. This post goes a little further and talks about how to use Structured Data with Docusaurus and MDX. More specifically it looks at how to create a component that both renders FAQs into a page, and the same information as Structured Data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eQSy9BtP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-04-08-docusaurus-structured-data-faqs-mdx/title-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eQSy9BtP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-04-08-docusaurus-structured-data-faqs-mdx/title-image.png" alt='title image reading "Docusaurus: Structured Data FAQs with MDX" with a Docusaurus logo' width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs and Structured Data
&lt;/h2&gt;

&lt;p&gt;I've been working with &lt;a href="https://growtika.com/"&gt;Growtika&lt;/a&gt; to repair my SEO after &lt;a href="//../2023-01-15-how-i-ruined-my-seo/index.md"&gt;shredding it somehow last year&lt;/a&gt;. One of the experiments we ran was to add &lt;a href="https://johnnyreilly.com/2023/02/01/migrating-from-github-pages-to-azure-static-web-apps"&gt;FAQs to a post&lt;/a&gt;, and with that, the equivalent FAQ Structured Data. The intent being to see if this would help with the SEO for that post.&lt;/p&gt;

&lt;p&gt;My blog is written in &lt;a href="https://mdxjs.com/"&gt;MDX&lt;/a&gt;, and hosted on &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt;. I wanted to see if I could create an MDX component that would render the FAQs into a page, and the same information as Structured Data. The &lt;a href="https://docusaurus.io/docs/markdown-features/react"&gt;Docusaurus docs suggested this was feasible&lt;/a&gt;, and I wanted to see if I could make it work.&lt;/p&gt;

&lt;p&gt;And it turns out that other people are interested in this too; there's a feature request on &lt;a href="https://docusaurus.io/feature-requests/p/creation-of-structured-faq"&gt;Docusaurus's Canny&lt;/a&gt; for exactly this.&lt;/p&gt;

&lt;p&gt;So I created a component that could be used to render FAQs into a page as markdown, and the same information as Structured Data. I thought it would be useful to share that component with the world. Hello world, herewith the component:&lt;/p&gt;

&lt;h2&gt;
  
  
  The FAQStructuredData MDX component
&lt;/h2&gt;

&lt;p&gt;I created a directory called &lt;code&gt;FAQStructuredData&lt;/code&gt; in the `&lt;code&gt;src/theme/MDXComponents&lt;/code&gt; directory. This directory is where I keep my custom MDX components. I then created an &lt;code&gt;index.js&lt;/code&gt; file in that directory. This is the file that contains the component:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;jsx title="src/theme/MDXComponents/FAQStructuredData/index.js"&lt;br&gt;
/**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/typedef"&gt;@typedef&lt;/a&gt; { import('./types').FAQStructuredDataProps } FAQStructuredDataProps&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/typedef"&gt;@typedef&lt;/a&gt; { import('./types').FAQStructuredData } FAQStructuredData
*/&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;import React from 'react';&lt;/p&gt;

&lt;p&gt;/**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A component that renders a FAQ structured data and markdown entries
*&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/see"&gt;@see&lt;/a&gt; &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/faqpage"&gt;https://developers.google.com/search/docs/appearance/structured-data/faqpage&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {FAQStructuredDataProps} props&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;@returns&lt;br&gt;
&lt;em&gt;/&lt;br&gt;
export default function FAQStructuredData(props) {&lt;br&gt;
/&lt;/em&gt;* @type {FAQStructuredData} */ const faqStructuredData = {&lt;br&gt;
'&lt;a class="mentioned-user" href="https://dev.to/context"&gt;@context&lt;/a&gt;': '&lt;a href="https://schema.org"&gt;https://schema.org&lt;/a&gt;',&lt;br&gt;
'@type': 'FAQPage',&lt;br&gt;
mainEntity: props.faqs.map((faq) =&amp;gt; ({&lt;br&gt;
  '@type': 'Question',&lt;br&gt;
  name: faq.question,&lt;br&gt;
  acceptedAnswer: {&lt;br&gt;
    '@type': 'Answer',&lt;br&gt;
    text: faq.answer,&lt;br&gt;
  },&lt;br&gt;
})),&lt;br&gt;
};&lt;br&gt;
return (&lt;br&gt;
&amp;lt;&amp;gt;&lt;br&gt;
  &amp;lt;br&amp;gt;
    {JSON.stringify(faqStructuredData)}&amp;lt;br&amp;gt;
  &lt;/p&gt;


&lt;h2&gt;FAQs&lt;/h2&gt;

  {faqStructuredData.mainEntity.map((faq) =&amp;gt; (
    
      &lt;h3&gt;{faq.name}&lt;/h3&gt;

      {faq.acceptedAnswer.text}
    
  ))}
&amp;lt;/&amp;gt;
);
}
&lt;code&gt;&lt;/code&gt;`&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;ts title="src/theme/MDXComponents/FAQStructuredData/types.d.ts"&lt;br&gt;
export interface FAQ {&lt;br&gt;
  question: string;&lt;br&gt;
  answer: string;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export interface FAQStructuredDataProps {&lt;br&gt;
  faqs: FAQ[];&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export interface FAQStructuredData {&lt;br&gt;
  '&lt;a class="mentioned-user" href="https://dev.to/context"&gt;@context&lt;/a&gt;': string;&lt;br&gt;
  '@type': string;&lt;br&gt;
  mainEntity: FAQQuestionStructuredData[];&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export interface FAQQuestionStructuredData {&lt;br&gt;
  '@type': 'Question';&lt;br&gt;
  name: string;&lt;br&gt;
  acceptedAnswer: {&lt;br&gt;
    '@type': 'Answer';&lt;br&gt;
    text: string;&lt;br&gt;
  };&lt;br&gt;
}&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;Some things to note about this component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code is written in JavaScript, but it's using &lt;a href="https://johnnyreilly.com/2021/11/22/typescript-vs-jsdoc-javascript"&gt;TypeScript types via JSDoc&lt;/a&gt;. I don't believe you can write MDX components in TypeScript (please someone let me know if it turns out this is possible). But static typing is useful, and still possible thanks to JSDoc.&lt;/li&gt;
&lt;li&gt;On that, we have a &lt;code&gt;types.d.ts&lt;/code&gt; file that contains the types for the component. Using TypeScript directly is still possible alongside JSDoc, as long as there is no runtime code in the file, and a definition file (which the &lt;code&gt;types.d.ts&lt;/code&gt; file is), has no runtime code. We can simply use it to store types that we import into the component.&lt;/li&gt;
&lt;li&gt;The component expects an &lt;code&gt;faqs&lt;/code&gt; prop. This is an array of FAQs. Each FAQ is an object with a &lt;code&gt;question&lt;/code&gt; and &lt;code&gt;answer&lt;/code&gt; property. The component then renders the FAQs as markdown, and the same information as JSON-LD Structured Data. We're using the Google guidelines for &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/faqpage#examples"&gt;FAQ Structured Data&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The component renders a &lt;code&gt;h2&lt;/code&gt; tag titled "FAQs". Under that, each FAQ is rendered with a &lt;code&gt;h3&lt;/code&gt; tag and the answer sits directly below it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Importing our MDX component
&lt;/h2&gt;

&lt;p&gt;Now the &lt;code&gt;FAQStructuredData&lt;/code&gt; component is created, we can use it in our MDX (or straight MD) files. We can import the component and create an array called &lt;code&gt;faqs&lt;/code&gt; like so:&lt;/p&gt;

&lt;h2&gt;
  
  
  `&lt;code&gt;&lt;/code&gt;mdx
&lt;/h2&gt;

&lt;p&gt;slug: docusaurus-structured-data-faqs-mdx&lt;br&gt;
title: 'Docusaurus: Structured Data FAQs with MDX'&lt;br&gt;
authors: johnnyreilly&lt;br&gt;
tags: [Docusaurus, Structured Data]&lt;br&gt;
image: ./title-image.png&lt;br&gt;
description: 'This demos how to make an MDX component that renders FAQs into a page, and the same information as Structured Data. It also shows how to use it with Docusaurus.'&lt;/p&gt;

&lt;h2&gt;
  
  
  hide_table_of_contents: false
&lt;/h2&gt;

&lt;p&gt;import FAQStructuredData from '../../src/theme/MDXComponents/FAQStructuredData';&lt;/p&gt;

&lt;p&gt;export const faqs = [&lt;br&gt;
  {&lt;br&gt;
    question: 'How do I use the FAQ Structured Data component?',&lt;br&gt;
    answer:&lt;br&gt;
      "Simply create an import statement for the component and then use it in your MDX file. You'll need to pass in an array of FAQs. This array can be inline, you can declare it as a variable, or you can import it from another file.",&lt;br&gt;
  },&lt;br&gt;
  {&lt;br&gt;
    question: 'How do I use the FAQ Structured Data component in a blog post?',&lt;br&gt;
    answer:&lt;br&gt;
      'The usage is the same as in a regular MDX file. But the import statement will sit after the frontmatter of the blog post.',&lt;br&gt;
  },&lt;br&gt;
  {&lt;br&gt;
    question:&lt;br&gt;
      'Can I use the FAQ Structured Data component in a regular MD file?',&lt;br&gt;
    answer: 'Yes! It just works™️.',&lt;br&gt;
  },&lt;br&gt;
];&lt;/p&gt;

&lt;p&gt;;&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;Note we're doing this in a blog post and the import statement is after the frontmatter. Then we can use the component like so:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`mdx&lt;br&gt;
&amp;lt;FAQStructuredData faqs={faqs} /&amp;gt;&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We'll do that, right here, right now:&lt;/p&gt;



&lt;h2&gt;
  
  
  Testing the Structured Data
&lt;/h2&gt;

&lt;p&gt;You can see, we have FAQs rendered in the body of our blog post. If we put the URL of the post into the &lt;a href="https://search.google.com/test/rich-results?url=https%3A%2F%2Fjohnnyreilly.com%2Fdocusaurus-structured-data-faqs-mdx"&gt;Google Structured Data Testing Tool&lt;/a&gt;, we can see that the Structured Data is being rendered correctly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ILlc5Sph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-04-08-docusaurus-structured-data-faqs-mdx/screenshot-rich-results-test.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ILlc5Sph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-04-08-docusaurus-structured-data-faqs-mdx/screenshot-rich-results-test.webp" alt="Screenshot of rich results test showing FAQs are detected" width="686" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can even go one better, shortly after I posted this article, I did a search in Google for "how do you have Docusaurus with Structured Data FAQs with MDX" and I got this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RyM03ooh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-04-08-docusaurus-structured-data-faqs-mdx/screenshot-featured-snippets-faqs.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RyM03ooh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/johnnyreilly/blog.johnnyreilly.com/main/blog-website/blog/2023-04-08-docusaurus-structured-data-faqs-mdx/screenshot-featured-snippets-faqs.webp" alt="A screenshot of the Google search window with the search 'how do you have Docusaurus with Structured Data FAQs with MDX' and the FAQs showing up as a featured snippet" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's our FAQs being surfaced as a &lt;a href="https://support.google.com/websearch/answer/9351707?hl=en-GB&amp;amp;visit_id=638180439903372599-4066254776&amp;amp;p=featured_snippets&amp;amp;rd=1#zippy=%2Cwhy-featured-snippets-may-be-removed"&gt;featured snippet&lt;/a&gt;. Nice!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We've now got a reusable FAQs component that renders the FAQs as markdown, and the same information as Structured Data. We can use it in our MDX files, and we can use it in our blog posts. We can also use it in regular MD files. Yay! I've only used this in the context of Docusaurus, but I suspect it can be used in other contexts too.&lt;/p&gt;

&lt;p&gt;I'd rather like it if this was built into Docusaurus, and if it could read directly from the Markdown files. But this is a good start. I hope you find it useful.&lt;/p&gt;

</description>
      <category>docusaurus</category>
      <category>structureddata</category>
    </item>
  </channel>
</rss>
