Sorry

This feed does not validate.

In addition, interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: http://feeds.feedburner.com/estruyf

  1. <?xml version="1.0" encoding="utf-8" standalone="yes"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:georss="http://www.georss.org/georss" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" version="2.0"><channel><title>Elio Struyf</title><link>https://www.eliostruyf.com/</link><description>SharePoint Branding &amp; Development Blog</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 22 Apr 2024 11:23:02 +0000</lastBuildDate><atom:link href="https://www.eliostruyf.com/feed.xml" rel="self" type="application/rss+xml"/><item><title>Fix admin consent for SP token retrieval flows in SPFx</title><link>https://www.eliostruyf.com/fix-admin-consent-sp-token-retrieval-flows-spfx/</link><pubDate>Mon, 22 Apr 2024 11:23:02 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/fix-admin-consent-sp-token-retrieval-flows-spfx/</guid><description>There have been a couple of changes in SharePoint recently related to retrieving access tokens for your SharePoint Framework solutions. One of the changes is that MSAL V3 now uses the /_api/Microsoft.SharePoint.Internal.ClientSideComponent.Token.AcquireOBOToken API to retrieve the access token. Typically, this API was only used when loading your solution from Microsoft Teams, but it will now also be used when loading your solution from SharePoint.</description><content:encoded>&lt;p&gt;There have been a couple of changes in SharePoint recently related to retrieving access tokens for your SharePoint Framework solutions. One of the changes is that MSAL V3 now uses the &lt;code&gt;/_api/Microsoft.SharePoint.Internal.ClientSideComponent.Token.AcquireOBOToken&lt;/code&gt; API to retrieve the access token. Typically, this API was only used when loading your solution from Microsoft Teams, but it will now also be used when loading your solution from SharePoint.&lt;/p&gt;
  2. &lt;p&gt;Due to this change, one customer started to experience issues with their SPFx solution. The solution used the &lt;code&gt;@pnp/graph&lt;/code&gt; library to retrieve the access token and call the Microsoft Graph API. They noticed the solution was no longer working and were redirected to the &lt;code&gt;/_forms/spfxsinglesignon.aspx&lt;/code&gt; page.&lt;/p&gt;
  3. &lt;p&gt;The &lt;code&gt;/_forms/spfxsinglesignon.aspx&lt;/code&gt; page is used to overcome an issue with third-party cookies or when something goes wrong with token retrieval.&lt;/p&gt;
  4. &lt;blockquote class='info'&gt;
  5. &lt;div class='mb-2'&gt;
  6. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  7. &lt;/div&gt;
  8. &lt;p&gt;
  9. &lt;span&gt;You can read more about it in the &lt;a href="https://www.eliostruyf.com/browser-refreshing-sharepoint-page/"&gt;Help my browser keeps refreshing my SharePoint page&lt;/a&gt; article.&lt;/span&gt;
  10. &lt;/p&gt;
  11. &lt;/blockquote&gt;
  12. &lt;h2 id="the-issue"&gt;The issue&lt;/h2&gt;
  13. &lt;p&gt;When looking into the issue, I saw failing calls to &lt;code&gt;/_api/Microsoft.SharePoint.Internal.ClientSideComponent.Token.AcquireOBOToken?resource=&amp;quot;https://graph.microsoft.com&amp;quot;&amp;amp;clientId=&amp;quot;72b90cbc-8519-4213-8c4a-1f3527b9f5f8&amp;quot;&lt;/code&gt;.&lt;/p&gt;
  14. &lt;p&gt;The error message that was returned was the following:&lt;/p&gt;
  15. &lt;figure class="codeblock_titled codeblock_wrapped"&gt;
  16. &lt;figcaption class="codeblock_titled__header"&gt;Token retrieval error&lt;/figcaption&gt;
  17. &lt;div class="highlight" title="Token retrieval error" wrap="true"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  18. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;odata.error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  19. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;10001&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  20. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  21. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;lang&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;en-US&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  22. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;AADSTS65001: The user or administrator has not consented to use the application with ID &amp;#39;00000003-0000-0ff1-ce00-000000000000&amp;#39; named &amp;#39;Office 365 SharePoint Online&amp;#39;. Send an interactive authorization request for this user and resource. Trace ID: 322f4528-fb10-4407-89e4-9de76da38900 Correlation ID: 827721a1-4047-8000-9395-cd41d1d50a48 Timestamp: 2024-04-22 10:12:52Z&amp;#34;&lt;/span&gt;
  23. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  24. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  25. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  26. &lt;/figure&gt;
  27. &lt;blockquote class='info'&gt;
  28. &lt;div class='mb-2'&gt;
  29. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  30. &lt;/div&gt;
  31. &lt;p&gt;
  32. &lt;span&gt;The &lt;code&gt;72b90cbc-8519-4213-8c4a-1f3527b9f5f8&lt;/code&gt; client ID, is the &lt;code&gt;SharePoint Online Client Extensibility Web Application Principal&lt;/code&gt; Entra app in my tenant, and this app is used by SharePoint Framework solutions to retrieve the access token.&lt;/span&gt;
  33. &lt;/p&gt;
  34. &lt;/blockquote&gt;
  35. &lt;p&gt;What was weird is that the error message mentioned the &lt;code&gt;00000003-0000-0ff1-ce00-000000000000&lt;/code&gt; or &lt;code&gt;Office 365 SharePoint Online&lt;/code&gt; application ID, as this trust should be automatically set.&lt;/p&gt;
  36. &lt;p&gt;When the page got redirected to the &lt;code&gt;/_forms/spfxsinglesignon.aspx&lt;/code&gt; page, the following message was returned in the query string:&lt;/p&gt;
  37. &lt;figure class="codeblock_titled codeblock_wrapped"&gt;
  38. &lt;figcaption class="codeblock_titled__header"&gt;Token retrieval error&lt;/figcaption&gt;
  39. &lt;div class="highlight" title="Token retrieval error" wrap="true"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AADSTS650057 Invalid resource. The client has requested access to a resource that is not listed in the requested permissions in the client&amp;#39;s application registration. Client app ID 08e18876-6177-487e-b8b5-cf950c1e598c (SharePoint Online Web Client Extensibility). Resource value from request 72b90cbc-8519-4213-8c4a-1f3527b9f5f8.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  40. &lt;/figure&gt;
  41. &lt;h2 id="the-solution"&gt;The solution&lt;/h2&gt;
  42. &lt;p&gt;The issue was that the &lt;code&gt;SharePoint Online Client Extensibility Web Application Principal&lt;/code&gt; Entra app was missing the &lt;strong&gt;Authorized client applications&lt;/strong&gt; for the &lt;code&gt;SharePoint Online Web Client Extensibility&lt;/code&gt; app and &lt;code&gt;Office 365 SharePoint Online&lt;/code&gt;.&lt;/p&gt;
  43. &lt;div class="caption my-4"&gt;
  44. &lt;figure class="caption__figure"&gt;
  45. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/04/missing-authorized-client-apps.webp" title="Show image"&gt;
  46. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  47. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk&amp;#43;M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" src="https://www.eliostruyf.com/uploads/2024/04/missing-authorized-client-apps.webp" alt="Missing authorized client apps" style="width: auto;" class="lazyload" /&gt;
  48. &lt;/a&gt;
  49. &lt;figcaption class="caption__text"&gt;Missing authorized client apps&lt;/figcaption&gt;
  50. &lt;/figure&gt;
  51. &lt;/div&gt;
  52. &lt;p&gt;To fix this issue, you need to add the following client IDs to the &lt;strong&gt;Authorized client applications&lt;/strong&gt; of the &lt;code&gt;SharePoint Online Client Extensibility Web Application Principal&lt;/code&gt; app:&lt;/p&gt;
  53. &lt;ul&gt;
  54. &lt;li&gt;&lt;code&gt;08e18876-6177-487e-b8b5-cf950c1e598c&lt;/code&gt; (SharePoint Online Web Client Extensibility)&lt;/li&gt;
  55. &lt;li&gt;&lt;code&gt;00000003-0000-0ff1-ce00-000000000000&lt;/code&gt; (Office 365 SharePoint Online)&lt;/li&gt;
  56. &lt;li&gt;&lt;code&gt;1fec8e78-bce4-4aaf-ab1b-5451cc387264&lt;/code&gt; (Microsoft Teams)&lt;/li&gt;
  57. &lt;li&gt;&lt;code&gt;5e3ce6c0-2b1f-4285-8d4b-75ee78787346&lt;/code&gt; (Microsoft Teams Web Client)&lt;/li&gt;
  58. &lt;/ul&gt;
  59. &lt;div class="caption my-4"&gt;
  60. &lt;figure class="caption__figure"&gt;
  61. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/04/authorized-client-apps.webp" title="Show image"&gt;
  62. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  63. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk&amp;#43;M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" src="https://www.eliostruyf.com/uploads/2024/04/authorized-client-apps.webp" alt="Authorized client apps" style="width: auto;" class="lazyload" /&gt;
  64. &lt;/a&gt;
  65. &lt;figcaption class="caption__text"&gt;Authorized client apps&lt;/figcaption&gt;
  66. &lt;/figure&gt;
  67. &lt;/div&gt;
  68. &lt;p&gt;I do not know why those apps were missing from the configuration. They should normally be automatically added when accessing the SharePoint admin API access page. The solution started to work again once the first two client IDs were added.&lt;/p&gt;
  69. &lt;div class="caption my-4"&gt;
  70. &lt;figure class="caption__figure"&gt;
  71. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/04/succesfull-token-retrieval.webp" title="Show image"&gt;
  72. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  73. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk&amp;#43;M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" src="https://www.eliostruyf.com/uploads/2024/04/succesfull-token-retrieval.webp" alt="Successful token retrieval" style="width: auto;" class="lazyload" /&gt;
  74. &lt;/a&gt;
  75. &lt;figcaption class="caption__text"&gt;Successful token retrieval&lt;/figcaption&gt;
  76. &lt;/figure&gt;
  77. &lt;/div&gt;</content:encoded></item><item><title>Use Playwright with Microsoft Dev Proxy on GitHub Actions</title><link>https://www.eliostruyf.com/playwright-microsoft-dev-proxy-github-actions/</link><pubDate>Fri, 29 Mar 2024 09:50:00 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/playwright-microsoft-dev-proxy-github-actions/</guid><description>Part of the process of testing the Microsoft Dev Proxy on GitHub Actions, was to use it in combination with Playwright. The advantage of this combination is that you can use the same mocked API responses which you use during development to test your solutions.
  78. info That way you do not have to write additional code to mock your APIs in Playwright like I explained in the Test the unexpected API results with Playwright mocking article.</description><content:encoded>&lt;p&gt;Part of the process of testing the &lt;a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/overview"&gt;Microsoft Dev Proxy&lt;/a&gt; on GitHub Actions, was to use it in combination with &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt;. The advantage of this combination is that you can use the same mocked API responses which you use during development to test your solutions.&lt;/p&gt;
  79. &lt;blockquote class='info'&gt;
  80. &lt;div class='mb-2'&gt;
  81. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  82. &lt;/div&gt;
  83. &lt;p&gt;
  84. &lt;span&gt;That way you do not have to write additional code to mock your APIs in Playwright like I explained in the &lt;a href="https://www.eliostruyf.com/test-unexpected-api-results-playwright-mocking/"&gt;Test the unexpected API results with Playwright mocking&lt;/a&gt; article.&lt;/span&gt;
  85. &lt;/p&gt;
  86. &lt;/blockquote&gt;
  87. &lt;p&gt;In this blog post, I will show you how to use Playwright in combination with the Microsoft Dev Proxy in your GitHub Actions workflow.&lt;/p&gt;
  88. &lt;blockquote class='important'&gt;
  89. &lt;div class='mb-2'&gt;
  90. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  91. &lt;/div&gt;
  92. &lt;p&gt;
  93. &lt;span&gt;Before you can continue, make sure you followed the &lt;a href="https://playwright.dev/docs/intro"&gt;Playwright - Getting started&lt;/a&gt; instructions.&lt;/span&gt;
  94. &lt;/p&gt;
  95. &lt;/blockquote&gt;
  96. &lt;h2 id="things-to-know-before-you-start"&gt;Things to know before you start&lt;/h2&gt;
  97. &lt;p&gt;When creating tests, your goal is to aim to a desired outcome. The Dev Proxy can be used in various ways to simulate different scenarios. One of those is to simulate random errors (by using the &lt;code&gt;GenericRandomErrorPlugin&lt;/code&gt; plugin), which can be useful during local development, but not when you want to run your end-to-end tests.&lt;/p&gt;
  98. &lt;p&gt;Best practice is to use the Dev Proxy for mocking APIs or simulating specific scenarios during end-to-end tests. This way, you can ensure that your tests are predictable and reliable.&lt;/p&gt;
  99. &lt;h2 id="setting-up-playwright"&gt;Setting up Playwright&lt;/h2&gt;
  100. &lt;p&gt;When you followed the &lt;a href="https://playwright.dev/docs/intro"&gt;Playwright getting started&lt;/a&gt; guide, you should have a basic setup in place. When you want to use Playwright with the Dev Proxy, you can add the proxy configuration to the configuration. You can do this in your &lt;code&gt;playwright.config.ts&lt;/code&gt; file by adding the following code:&lt;/p&gt;
  101. &lt;figure class="codeblock_titled "&gt;
  102. &lt;figcaption class="codeblock_titled__header"&gt;playwright.config.ts&lt;/figcaption&gt;
  103. &lt;div class="highlight" title="playwright.config.ts"&gt;&lt;div class="chroma"&gt;
  104. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  105. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  106. &lt;/span&gt;&lt;span class="lnt"&gt;2
  107. &lt;/span&gt;&lt;span class="lnt"&gt;3
  108. &lt;/span&gt;&lt;span class="lnt"&gt;4
  109. &lt;/span&gt;&lt;span class="lnt"&gt;5
  110. &lt;/span&gt;&lt;span class="lnt"&gt;6
  111. &lt;/span&gt;&lt;span class="lnt"&gt;7
  112. &lt;/span&gt;&lt;span class="lnt"&gt;8
  113. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  114. &lt;td class="lntd"&gt;
  115. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@playwright/test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  116. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  117. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  118. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  119. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://localhost:8000&amp;#39;&lt;/span&gt;
  120. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  121. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  122. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  123. &lt;/div&gt;
  124. &lt;/div&gt;
  125. &lt;/figure&gt;
  126. &lt;h2 id="configuring-the-github-actions-workflow"&gt;Configuring the GitHub Actions workflow&lt;/h2&gt;
  127. &lt;p&gt;Once the configuration is in place, you can start configuring your GitHub Actions workflow.&lt;/p&gt;
  128. &lt;h3 id="using-a-macos-runner"&gt;Using a macOS runner&lt;/h3&gt;
  129. &lt;p&gt;When using the macOS runner, you can start with the configuration found in the &lt;a href="https://www.eliostruyf.com/dev-proxy-github-actions-workflow-macos/"&gt;Using Microsoft Dev Proxy in your GitHub Actions workflow on a macOS hosted VM&lt;/a&gt; article. The only thing you need to add is the dependency installation steps.&lt;/p&gt;
  130. &lt;figure class="codeblock_titled "&gt;
  131. &lt;figcaption class="codeblock_titled__header"&gt;GitHub Actions workflow - macOS runner&lt;/figcaption&gt;
  132. &lt;div class="highlight" title="GitHub Actions workflow - macOS runner"&gt;&lt;div class="chroma"&gt;
  133. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  134. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  135. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  136. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  137. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  138. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  139. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  140. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  141. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  142. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  143. &lt;/span&gt;&lt;span class="lnt"&gt;10
  144. &lt;/span&gt;&lt;span class="lnt"&gt;11
  145. &lt;/span&gt;&lt;span class="lnt"&gt;12
  146. &lt;/span&gt;&lt;span class="lnt"&gt;13
  147. &lt;/span&gt;&lt;span class="lnt"&gt;14
  148. &lt;/span&gt;&lt;span class="lnt"&gt;15
  149. &lt;/span&gt;&lt;span class="lnt"&gt;16
  150. &lt;/span&gt;&lt;span class="lnt"&gt;17
  151. &lt;/span&gt;&lt;span class="lnt"&gt;18
  152. &lt;/span&gt;&lt;span class="lnt"&gt;19
  153. &lt;/span&gt;&lt;span class="lnt"&gt;20
  154. &lt;/span&gt;&lt;span class="lnt"&gt;21
  155. &lt;/span&gt;&lt;span class="lnt"&gt;22
  156. &lt;/span&gt;&lt;span class="lnt"&gt;23
  157. &lt;/span&gt;&lt;span class="lnt"&gt;24
  158. &lt;/span&gt;&lt;span class="lnt"&gt;25
  159. &lt;/span&gt;&lt;span class="lnt"&gt;26
  160. &lt;/span&gt;&lt;span class="lnt"&gt;27
  161. &lt;/span&gt;&lt;span class="lnt"&gt;28
  162. &lt;/span&gt;&lt;span class="lnt"&gt;29
  163. &lt;/span&gt;&lt;span class="lnt"&gt;30
  164. &lt;/span&gt;&lt;span class="lnt"&gt;31
  165. &lt;/span&gt;&lt;span class="lnt"&gt;32
  166. &lt;/span&gt;&lt;span class="lnt"&gt;33
  167. &lt;/span&gt;&lt;span class="lnt"&gt;34
  168. &lt;/span&gt;&lt;span class="lnt"&gt;35
  169. &lt;/span&gt;&lt;span class="lnt"&gt;36
  170. &lt;/span&gt;&lt;span class="lnt"&gt;37
  171. &lt;/span&gt;&lt;span class="lnt"&gt;38
  172. &lt;/span&gt;&lt;span class="lnt"&gt;39
  173. &lt;/span&gt;&lt;span class="lnt"&gt;40
  174. &lt;/span&gt;&lt;span class="lnt"&gt;41
  175. &lt;/span&gt;&lt;span class="lnt"&gt;42
  176. &lt;/span&gt;&lt;span class="lnt"&gt;43
  177. &lt;/span&gt;&lt;span class="lnt"&gt;44
  178. &lt;/span&gt;&lt;span class="lnt"&gt;45
  179. &lt;/span&gt;&lt;span class="lnt"&gt;46
  180. &lt;/span&gt;&lt;span class="lnt"&gt;47
  181. &lt;/span&gt;&lt;span class="lnt"&gt;48
  182. &lt;/span&gt;&lt;span class="lnt"&gt;49
  183. &lt;/span&gt;&lt;span class="lnt"&gt;50
  184. &lt;/span&gt;&lt;span class="lnt"&gt;51
  185. &lt;/span&gt;&lt;span class="lnt"&gt;52
  186. &lt;/span&gt;&lt;span class="lnt"&gt;53
  187. &lt;/span&gt;&lt;span class="lnt"&gt;54
  188. &lt;/span&gt;&lt;span class="lnt"&gt;55
  189. &lt;/span&gt;&lt;span class="lnt"&gt;56
  190. &lt;/span&gt;&lt;span class="lnt"&gt;57
  191. &lt;/span&gt;&lt;span class="lnt"&gt;58
  192. &lt;/span&gt;&lt;span class="lnt"&gt;59
  193. &lt;/span&gt;&lt;span class="lnt"&gt;60
  194. &lt;/span&gt;&lt;span class="lnt"&gt;61
  195. &lt;/span&gt;&lt;span class="lnt"&gt;62
  196. &lt;/span&gt;&lt;span class="lnt"&gt;63
  197. &lt;/span&gt;&lt;span class="lnt"&gt;64
  198. &lt;/span&gt;&lt;span class="lnt"&gt;65
  199. &lt;/span&gt;&lt;span class="lnt"&gt;66
  200. &lt;/span&gt;&lt;span class="lnt"&gt;67
  201. &lt;/span&gt;&lt;span class="lnt"&gt;68
  202. &lt;/span&gt;&lt;span class="lnt"&gt;69
  203. &lt;/span&gt;&lt;span class="lnt"&gt;70
  204. &lt;/span&gt;&lt;span class="lnt"&gt;71
  205. &lt;/span&gt;&lt;span class="lnt"&gt;72
  206. &lt;/span&gt;&lt;span class="lnt"&gt;73
  207. &lt;/span&gt;&lt;span class="lnt"&gt;74
  208. &lt;/span&gt;&lt;span class="lnt"&gt;75
  209. &lt;/span&gt;&lt;span class="lnt"&gt;76
  210. &lt;/span&gt;&lt;span class="lnt"&gt;77
  211. &lt;/span&gt;&lt;span class="lnt"&gt;78
  212. &lt;/span&gt;&lt;span class="lnt"&gt;79
  213. &lt;/span&gt;&lt;span class="lnt"&gt;80
  214. &lt;/span&gt;&lt;span class="lnt"&gt;81
  215. &lt;/span&gt;&lt;span class="lnt"&gt;82
  216. &lt;/span&gt;&lt;span class="lnt"&gt;83
  217. &lt;/span&gt;&lt;span class="lnt"&gt;84
  218. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  219. &lt;td class="lntd"&gt;
  220. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;macOS Dev Proxy with Playwright&lt;/span&gt;&lt;span class="w"&gt;
  221. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  222. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  223. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  224. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  225. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
  226. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
  227. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  228. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  229. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  230. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  231. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
  232. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;macos-latest&lt;/span&gt;&lt;span class="w"&gt;
  233. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  234. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
  235. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  236. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-node@v4&lt;/span&gt;&lt;span class="w"&gt;
  237. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  238. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cache&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;#34;npm&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  239. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  240. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install dependencies&lt;/span&gt;&lt;span class="w"&gt;
  241. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npm ci&lt;/span&gt;&lt;span class="w"&gt;
  242. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  243. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#################################&lt;/span&gt;&lt;span class="w"&gt;
  244. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Cache + install of Playwright #&lt;/span&gt;&lt;span class="w"&gt;
  245. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#################################&lt;/span&gt;&lt;span class="w"&gt;
  246. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Store Playwright&amp;#39;s Version&lt;/span&gt;&lt;span class="w"&gt;
  247. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  248. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; PLAYWRIGHT_VERSION=$(npm ls @playwright/test | grep @playwright | sed &amp;#39;s/.*@//&amp;#39;)
  249. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Playwright&amp;#39;s Version: $PLAYWRIGHT_VERSION&amp;#34;
  250. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;
  251. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  252. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Cache Playwright Browsers for Playwright&amp;#39;s Version&lt;/span&gt;&lt;span class="w"&gt;
  253. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cache-playwright&lt;/span&gt;&lt;span class="w"&gt;
  254. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/cache@v4&lt;/span&gt;&lt;span class="w"&gt;
  255. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  256. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;~/Library/Caches/ms-playwright&lt;/span&gt;&lt;span class="w"&gt;
  257. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;playwright-${{ env.PLAYWRIGHT_VERSION }}&lt;/span&gt;&lt;span class="w"&gt;
  258. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  259. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Playwright Browsers&lt;/span&gt;&lt;span class="w"&gt;
  260. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;steps.cache-playwright.outputs.cache-hit != &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
  261. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npx playwright install --with-deps&lt;/span&gt;&lt;span class="w"&gt;
  262. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  263. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;################################&lt;/span&gt;&lt;span class="w"&gt;
  264. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Cache + install of Dev Proxy #&lt;/span&gt;&lt;span class="w"&gt;
  265. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;################################&lt;/span&gt;&lt;span class="w"&gt;
  266. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Store Dev Proxy&amp;#39;s Version&lt;/span&gt;&lt;span class="w"&gt;
  267. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  268. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; DEVPROXY_VERSION=$(curl -s https://api.github.com/repos/microsoft/dev-proxy/releases/latest | jq .tag_name -r)
  269. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Dev Proxy&amp;#39;s Version: $DEVPROXY_VERSION&amp;#34;
  270. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;DEVPROXY_VERSION=$DEVPROXY_VERSION&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;
  271. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  272. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Cache Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  273. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cache-devproxy&lt;/span&gt;&lt;span class="w"&gt;
  274. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/cache@v4&lt;/span&gt;&lt;span class="w"&gt;
  275. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  276. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy&lt;/span&gt;&lt;span class="w"&gt;
  277. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;devproxy-${{ env.DEVPROXY_VERSION }}&lt;/span&gt;&lt;span class="w"&gt;
  278. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  279. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  280. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;steps.cache-devproxy.outputs.cache-hit != &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
  281. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash -c &amp;#34;$(curl -sL https://aka.ms/devproxy/setup.sh)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  282. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  283. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#######################&lt;/span&gt;&lt;span class="w"&gt;
  284. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Configure Dev Proxy #&lt;/span&gt;&lt;span class="w"&gt;
  285. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#######################&lt;/span&gt;&lt;span class="w"&gt;
  286. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  287. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy/devproxy &amp;amp;&lt;/span&gt;&lt;span class="w"&gt;
  288. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  289. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install the Dev Proxy&amp;#39;s certificate&lt;/span&gt;&lt;span class="w"&gt;
  290. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  291. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  292. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Finding certificate...&amp;#34;
  293. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; security find-certificate -c &amp;#34;Dev Proxy CA&amp;#34; -a -p &amp;gt; dev-proxy-ca.pem
  294. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  295. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Trusting certificate...&amp;#34;
  296. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain dev-proxy-ca.pem
  297. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Certificate trusted.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  298. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  299. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;##########################&lt;/span&gt;&lt;span class="w"&gt;
  300. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Start Playwright tests #&lt;/span&gt;&lt;span class="w"&gt;
  301. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;##########################&lt;/span&gt;&lt;span class="w"&gt;
  302. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Playwright tests&lt;/span&gt;&lt;span class="w"&gt;
  303. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npx playwright test&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  304. &lt;/div&gt;
  305. &lt;/div&gt;
  306. &lt;/figure&gt;
  307. &lt;div class="caption my-4"&gt;
  308. &lt;figure class="caption__figure"&gt;
  309. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/playwright-with-devproxy-macos.webp" title="Show image"&gt;
  310. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  311. &lt;img data-lqip="data:image/jpeg;base64,UklGRmgAAABXRUJQVlA4WAoAAAAQAAAACQAAAgAAQUxQSB4AAAABHyCQTfxNWxLXiIiQoSYAAca69c9A4Yj&amp;#43;x1ciXwFWUDggJAAAAHABAJ0BKgoAAwABQCYlnAJ0AUAAAP79BJovp&amp;#43;v8O9LqoVgAAA==" src="https://www.eliostruyf.com/uploads/2024/03/playwright-with-devproxy-macos.webp" alt="Playwright outcome with Dev Proxy on a macOS runner" style="width: 2084px;" class="lazyload" /&gt;
  312. &lt;/a&gt;
  313. &lt;figcaption class="caption__text"&gt;Playwright outcome with Dev Proxy on a macOS runner&lt;/figcaption&gt;
  314. &lt;/figure&gt;
  315. &lt;/div&gt;
  316. &lt;h3 id="using-an-ubuntu-runner"&gt;Using an Ubuntu runner&lt;/h3&gt;
  317. &lt;p&gt;Things are a bit more complicated when you want to use the Dev Proxy with Playwright on an Ubuntu virtual machine/runner. The reason is the way browsers manage certificates on Linux. On macOS, all browsers use the certificates wich are trusted in the Keychain, but on Linux, each browser has its own certificate store which complicates things.&lt;/p&gt;
  318. &lt;p&gt;Chromium uses the &lt;a href="https://chromium.googlesource.com/chromium/src/+/master/docs/linux/cert_management.md"&gt;NSS Shared DB&lt;/a&gt; to manage certificates.&lt;/p&gt;
  319. &lt;p&gt;Firefox does not have a central location where it looks for certificates. It will use the current profile or you can assign a &lt;a href="https://mozilla.github.io/policy-templates/"&gt;policy template&lt;/a&gt; to manage certificates.&lt;/p&gt;
  320. &lt;p&gt;When using Firefox with Playwright, it creates an in-memory profile, so you cannot add the certificate to the profile before starting the browser. The policy approach is not yet supported by Playwright &lt;a href="https://github.com/microsoft/playwright/issues/28967"&gt;Playwright issue #28967&lt;/a&gt;. So, in case of using Firefox, you will have to ignore the HTTPS errors.&lt;/p&gt;
  321. &lt;h4 id="ignoring-https-errors-the-simple-approach"&gt;Ignoring HTTPS errors (the simple approach)&lt;/h4&gt;
  322. &lt;p&gt;The simple approach is to ignore HTTPS errors by setting the &lt;code&gt;ignoreHTTPSErrors&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in your Playwright configuration. You can do this for all browsers or for specific browsers. Here is an example of how you can do this:&lt;/p&gt;
  323. &lt;figure class="codeblock_titled "&gt;
  324. &lt;figcaption class="codeblock_titled__header"&gt;playwright.config.ts&lt;/figcaption&gt;
  325. &lt;div class="highlight" title="playwright.config.ts"&gt;&lt;div class="chroma"&gt;
  326. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  327. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  328. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  329. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  330. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  331. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  332. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  333. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  334. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  335. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  336. &lt;/span&gt;&lt;span class="lnt"&gt;10
  337. &lt;/span&gt;&lt;span class="lnt"&gt;11
  338. &lt;/span&gt;&lt;span class="lnt"&gt;12
  339. &lt;/span&gt;&lt;span class="lnt"&gt;13
  340. &lt;/span&gt;&lt;span class="lnt"&gt;14
  341. &lt;/span&gt;&lt;span class="lnt"&gt;15
  342. &lt;/span&gt;&lt;span class="lnt"&gt;16
  343. &lt;/span&gt;&lt;span class="lnt"&gt;17
  344. &lt;/span&gt;&lt;span class="lnt"&gt;18
  345. &lt;/span&gt;&lt;span class="lnt"&gt;19
  346. &lt;/span&gt;&lt;span class="lnt"&gt;20
  347. &lt;/span&gt;&lt;span class="lnt"&gt;21
  348. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  349. &lt;td class="lntd"&gt;
  350. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@playwright/test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  351. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  352. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  353. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  354. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  355. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://localhost:8000&amp;#39;&lt;/span&gt;
  356. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  357. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Ignore all for all browsers
  358. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="nx"&gt;ignoreHTTPSErrors&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;
  359. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  360. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  361. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  362. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;firefox&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  363. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  364. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Firefox Desktop&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  365. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Ignore HTTPS errors for Firefox only
  366. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="nx"&gt;ignoreHTTPSErrors&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;
  367. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  368. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  369. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
  370. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  371. &lt;/div&gt;
  372. &lt;/div&gt;
  373. &lt;/figure&gt;
  374. &lt;h4 id="adding-the-certificate-to-the-nss-shared-db"&gt;Adding the certificate to the NSS Shared DB&lt;/h4&gt;
  375. &lt;p&gt;If you are only using Chromium in your tests, you can add the certificate to the Chromium &lt;a href="https://chromium.googlesource.com/chromium/src/+/master/docs/linux/cert_management.md"&gt;NSS Shared DB&lt;/a&gt;.&lt;/p&gt;
  376. &lt;p&gt;To add the certificate to the NSS Shared DB, you can use the following steps:&lt;/p&gt;
  377. &lt;figure class="codeblock_titled "&gt;
  378. &lt;figcaption class="codeblock_titled__header"&gt;Add certificate to NSS Shared DB - GitHub Actions steps&lt;/figcaption&gt;
  379. &lt;div class="highlight" title="Add certificate to NSS Shared DB - GitHub Actions steps"&gt;&lt;div class="chroma"&gt;
  380. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  381. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  382. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  383. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  384. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  385. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  386. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  387. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  388. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  389. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  390. &lt;/span&gt;&lt;span class="lnt"&gt;10
  391. &lt;/span&gt;&lt;span class="lnt"&gt;11
  392. &lt;/span&gt;&lt;span class="lnt"&gt;12
  393. &lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;13
  394. &lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;14
  395. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  396. &lt;td class="lntd"&gt;
  397. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install the Dev Proxy certificate for Chromium&lt;/span&gt;&lt;span class="w"&gt;
  398. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  399. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  400. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Export the Dev Proxy&amp;#39;s Root Certificate&amp;#34;
  401. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; openssl pkcs12 -in ~/.config/dev-proxy/rootCert.pfx -clcerts -nokeys -out dev-proxy-ca.crt -passin pass:&amp;#34;&amp;#34;
  402. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  403. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # echo &amp;#34;Installing certutil&amp;#34;
  404. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo apt install libnss3-tools
  405. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  406. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # echo &amp;#34;Adding certificate to the NSS database for Chromium&amp;#34;
  407. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mkdir -p $HOME/.pki/nssdb
  408. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; certutil --empty-password -d $HOME/.pki/nssdb -N
  409. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; certutil -d sql:$HOME/.pki/nssdb -A -t &amp;#34;CT,,&amp;#34; -n dev-proxy-ca.crt -i dev-proxy-ca.crt
  410. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Certificate trusted.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  411. &lt;/div&gt;
  412. &lt;/div&gt;
  413. &lt;/figure&gt;
  414. &lt;p&gt;The highlighted line in the code snippet above shows how you can add the &lt;code&gt;dev-proxy-ca.crt&lt;/code&gt; certificate to the NSS Shared DB of Chromium. The &lt;code&gt;CT,,&lt;/code&gt; trust arguments add the trusted for SSL client authentication.&lt;/p&gt;
  415. &lt;p&gt;Here is the complete GitHub Actions workflow for Ubuntu:&lt;/p&gt;
  416. &lt;figure class="codeblock_titled "&gt;
  417. &lt;figcaption class="codeblock_titled__header"&gt;GitHub Actions workflow - Ubuntu runner&lt;/figcaption&gt;
  418. &lt;div class="highlight" title="GitHub Actions workflow - Ubuntu runner"&gt;&lt;div class="chroma"&gt;
  419. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  420. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  421. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  422. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  423. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  424. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  425. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  426. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  427. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  428. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  429. &lt;/span&gt;&lt;span class="lnt"&gt;10
  430. &lt;/span&gt;&lt;span class="lnt"&gt;11
  431. &lt;/span&gt;&lt;span class="lnt"&gt;12
  432. &lt;/span&gt;&lt;span class="lnt"&gt;13
  433. &lt;/span&gt;&lt;span class="lnt"&gt;14
  434. &lt;/span&gt;&lt;span class="lnt"&gt;15
  435. &lt;/span&gt;&lt;span class="lnt"&gt;16
  436. &lt;/span&gt;&lt;span class="lnt"&gt;17
  437. &lt;/span&gt;&lt;span class="lnt"&gt;18
  438. &lt;/span&gt;&lt;span class="lnt"&gt;19
  439. &lt;/span&gt;&lt;span class="lnt"&gt;20
  440. &lt;/span&gt;&lt;span class="lnt"&gt;21
  441. &lt;/span&gt;&lt;span class="lnt"&gt;22
  442. &lt;/span&gt;&lt;span class="lnt"&gt;23
  443. &lt;/span&gt;&lt;span class="lnt"&gt;24
  444. &lt;/span&gt;&lt;span class="lnt"&gt;25
  445. &lt;/span&gt;&lt;span class="lnt"&gt;26
  446. &lt;/span&gt;&lt;span class="lnt"&gt;27
  447. &lt;/span&gt;&lt;span class="lnt"&gt;28
  448. &lt;/span&gt;&lt;span class="lnt"&gt;29
  449. &lt;/span&gt;&lt;span class="lnt"&gt;30
  450. &lt;/span&gt;&lt;span class="lnt"&gt;31
  451. &lt;/span&gt;&lt;span class="lnt"&gt;32
  452. &lt;/span&gt;&lt;span class="lnt"&gt;33
  453. &lt;/span&gt;&lt;span class="lnt"&gt;34
  454. &lt;/span&gt;&lt;span class="lnt"&gt;35
  455. &lt;/span&gt;&lt;span class="lnt"&gt;36
  456. &lt;/span&gt;&lt;span class="lnt"&gt;37
  457. &lt;/span&gt;&lt;span class="lnt"&gt;38
  458. &lt;/span&gt;&lt;span class="lnt"&gt;39
  459. &lt;/span&gt;&lt;span class="lnt"&gt;40
  460. &lt;/span&gt;&lt;span class="lnt"&gt;41
  461. &lt;/span&gt;&lt;span class="lnt"&gt;42
  462. &lt;/span&gt;&lt;span class="lnt"&gt;43
  463. &lt;/span&gt;&lt;span class="lnt"&gt;44
  464. &lt;/span&gt;&lt;span class="lnt"&gt;45
  465. &lt;/span&gt;&lt;span class="lnt"&gt;46
  466. &lt;/span&gt;&lt;span class="lnt"&gt;47
  467. &lt;/span&gt;&lt;span class="lnt"&gt;48
  468. &lt;/span&gt;&lt;span class="lnt"&gt;49
  469. &lt;/span&gt;&lt;span class="lnt"&gt;50
  470. &lt;/span&gt;&lt;span class="lnt"&gt;51
  471. &lt;/span&gt;&lt;span class="lnt"&gt;52
  472. &lt;/span&gt;&lt;span class="lnt"&gt;53
  473. &lt;/span&gt;&lt;span class="lnt"&gt;54
  474. &lt;/span&gt;&lt;span class="lnt"&gt;55
  475. &lt;/span&gt;&lt;span class="lnt"&gt;56
  476. &lt;/span&gt;&lt;span class="lnt"&gt;57
  477. &lt;/span&gt;&lt;span class="lnt"&gt;58
  478. &lt;/span&gt;&lt;span class="lnt"&gt;59
  479. &lt;/span&gt;&lt;span class="lnt"&gt;60
  480. &lt;/span&gt;&lt;span class="lnt"&gt;61
  481. &lt;/span&gt;&lt;span class="lnt"&gt;62
  482. &lt;/span&gt;&lt;span class="lnt"&gt;63
  483. &lt;/span&gt;&lt;span class="lnt"&gt;64
  484. &lt;/span&gt;&lt;span class="lnt"&gt;65
  485. &lt;/span&gt;&lt;span class="lnt"&gt;66
  486. &lt;/span&gt;&lt;span class="lnt"&gt;67
  487. &lt;/span&gt;&lt;span class="lnt"&gt;68
  488. &lt;/span&gt;&lt;span class="lnt"&gt;69
  489. &lt;/span&gt;&lt;span class="lnt"&gt;70
  490. &lt;/span&gt;&lt;span class="lnt"&gt;71
  491. &lt;/span&gt;&lt;span class="lnt"&gt;72
  492. &lt;/span&gt;&lt;span class="lnt"&gt;73
  493. &lt;/span&gt;&lt;span class="lnt"&gt;74
  494. &lt;/span&gt;&lt;span class="lnt"&gt;75
  495. &lt;/span&gt;&lt;span class="lnt"&gt;76
  496. &lt;/span&gt;&lt;span class="lnt"&gt;77
  497. &lt;/span&gt;&lt;span class="lnt"&gt;78
  498. &lt;/span&gt;&lt;span class="lnt"&gt;79
  499. &lt;/span&gt;&lt;span class="lnt"&gt;80
  500. &lt;/span&gt;&lt;span class="lnt"&gt;81
  501. &lt;/span&gt;&lt;span class="lnt"&gt;82
  502. &lt;/span&gt;&lt;span class="lnt"&gt;83
  503. &lt;/span&gt;&lt;span class="lnt"&gt;84
  504. &lt;/span&gt;&lt;span class="lnt"&gt;85
  505. &lt;/span&gt;&lt;span class="lnt"&gt;86
  506. &lt;/span&gt;&lt;span class="lnt"&gt;87
  507. &lt;/span&gt;&lt;span class="lnt"&gt;88
  508. &lt;/span&gt;&lt;span class="lnt"&gt;89
  509. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  510. &lt;td class="lntd"&gt;
  511. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  512. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  513. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  514. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  515. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  516. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
  517. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
  518. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  519. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  520. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  521. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  522. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
  523. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
  524. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  525. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
  526. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  527. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-node@v4&lt;/span&gt;&lt;span class="w"&gt;
  528. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  529. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cache&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;#34;npm&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  530. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  531. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install dependencies&lt;/span&gt;&lt;span class="w"&gt;
  532. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npm ci&lt;/span&gt;&lt;span class="w"&gt;
  533. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  534. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#################################&lt;/span&gt;&lt;span class="w"&gt;
  535. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Cache + install of Playwright #&lt;/span&gt;&lt;span class="w"&gt;
  536. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#################################&lt;/span&gt;&lt;span class="w"&gt;
  537. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Store Playwright&amp;#39;s Version&lt;/span&gt;&lt;span class="w"&gt;
  538. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  539. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; PLAYWRIGHT_VERSION=$(npm ls @playwright/test | grep @playwright | sed &amp;#39;s/.*@//&amp;#39;)
  540. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Playwright&amp;#39;s Version: $PLAYWRIGHT_VERSION&amp;#34;
  541. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;
  542. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  543. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Cache Playwright Browsers for Playwright&amp;#39;s Version&lt;/span&gt;&lt;span class="w"&gt;
  544. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cache-playwright&lt;/span&gt;&lt;span class="w"&gt;
  545. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/cache@v4&lt;/span&gt;&lt;span class="w"&gt;
  546. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  547. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;~/.cache/ms-playwright&lt;/span&gt;&lt;span class="w"&gt;
  548. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;playwright-ubuntu-${{ env.PLAYWRIGHT_VERSION }}&lt;/span&gt;&lt;span class="w"&gt;
  549. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  550. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Playwright Browsers&lt;/span&gt;&lt;span class="w"&gt;
  551. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;steps.cache-playwright.outputs.cache-hit != &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
  552. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npx playwright install --with-deps&lt;/span&gt;&lt;span class="w"&gt;
  553. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  554. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;################################&lt;/span&gt;&lt;span class="w"&gt;
  555. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Cache + install of Dev Proxy #&lt;/span&gt;&lt;span class="w"&gt;
  556. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;################################&lt;/span&gt;&lt;span class="w"&gt;
  557. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Store Dev Proxy&amp;#39;s Version&lt;/span&gt;&lt;span class="w"&gt;
  558. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  559. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; DEVPROXY_VERSION=$(curl -s https://api.github.com/repos/microsoft/dev-proxy/releases/latest | jq .tag_name -r)
  560. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Dev Proxy&amp;#39;s Version: $DEVPROXY_VERSION&amp;#34;
  561. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;DEVPROXY_VERSION=$DEVPROXY_VERSION&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;
  562. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  563. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Cache Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  564. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cache-devproxy&lt;/span&gt;&lt;span class="w"&gt;
  565. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/cache@v4&lt;/span&gt;&lt;span class="w"&gt;
  566. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  567. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy&lt;/span&gt;&lt;span class="w"&gt;
  568. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;devproxy-ubuntu-${{ env.DEVPROXY_VERSION }}&lt;/span&gt;&lt;span class="w"&gt;
  569. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  570. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  571. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;steps.cache-devproxy.outputs.cache-hit != &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
  572. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash -c &amp;#34;$(curl -sL https://aka.ms/devproxy/setup.sh)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  573. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  574. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#######################&lt;/span&gt;&lt;span class="w"&gt;
  575. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Configure Dev Proxy #&lt;/span&gt;&lt;span class="w"&gt;
  576. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#######################&lt;/span&gt;&lt;span class="w"&gt;
  577. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  578. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy/devproxy &amp;amp;&lt;/span&gt;&lt;span class="w"&gt;
  579. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  580. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install the Dev Proxy certificate for Chromium&lt;/span&gt;&lt;span class="w"&gt;
  581. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  582. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  583. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Export the Dev Proxy&amp;#39;s Root Certificate&amp;#34;
  584. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; openssl pkcs12 -in ~/.config/dev-proxy/rootCert.pfx -clcerts -nokeys -out dev-proxy-ca.crt -passin pass:&amp;#34;&amp;#34;
  585. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  586. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # echo &amp;#34;Installing certutil&amp;#34;
  587. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo apt install libnss3-tools
  588. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  589. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # echo &amp;#34;Adding certificate to the NSS database for Chromium&amp;#34;
  590. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mkdir -p $HOME/.pki/nssdb
  591. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; certutil --empty-password -d $HOME/.pki/nssdb -N
  592. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; certutil -d sql:$HOME/.pki/nssdb -A -t &amp;#34;CT,,&amp;#34; -n dev-proxy-ca.crt -i dev-proxy-ca.crt
  593. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Certificate trusted.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  594. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  595. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;##########################&lt;/span&gt;&lt;span class="w"&gt;
  596. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Start Playwright tests #&lt;/span&gt;&lt;span class="w"&gt;
  597. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;##########################&lt;/span&gt;&lt;span class="w"&gt;
  598. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Playwright tests&lt;/span&gt;&lt;span class="w"&gt;
  599. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npx playwright test&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  600. &lt;/div&gt;
  601. &lt;/div&gt;
  602. &lt;/figure&gt;
  603. &lt;div class="caption my-4"&gt;
  604. &lt;figure class="caption__figure"&gt;
  605. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/playwright-with-devproxy-ubuntu.webp" title="Show image"&gt;
  606. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  607. &lt;img data-lqip="data:image/jpeg;base64,UklGRmgAAABXRUJQVlA4WAoAAAAQAAAACQAAAgAAQUxQSB4AAAABHyCQTfxNWxLXiIiQoSYAAca69c9A4Yj&amp;#43;x1ciXwFWUDggJAAAAHABAJ0BKgoAAwABQCYlnAJ0AUAAAP79BJovp&amp;#43;v8O9LqoVgAAA==" src="https://www.eliostruyf.com/uploads/2024/03/playwright-with-devproxy-ubuntu.webp" alt="Playwright outcome with Dev Proxy on an Ubuntu runner" style="width: 2084px;" class="lazyload" /&gt;
  608. &lt;/a&gt;
  609. &lt;figcaption class="caption__text"&gt;Playwright outcome with Dev Proxy on an Ubuntu runner&lt;/figcaption&gt;
  610. &lt;/figure&gt;
  611. &lt;/div&gt;
  612. &lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
  613. &lt;p&gt;Using Playwright in combination with the Microsoft Dev Proxy on GitHub Actions is possible, but as you can see it requires some additional steps when using an Ubuntu runner. The macOS runner is more straightforward to configure. Hopefully these configuration differences will change in the future, but for now I hope you can get started with the provided examples.&lt;/p&gt;
  614. &lt;blockquote class='info'&gt;
  615. &lt;div class='mb-2'&gt;
  616. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  617. &lt;/div&gt;
  618. &lt;p&gt;
  619. &lt;span&gt;Templates are available on the following &lt;a href="https://github.com/estruyf/devproxy-github-actions-templates"&gt;GitHub repository&lt;/a&gt;.&lt;/span&gt;
  620. &lt;/p&gt;
  621. &lt;/blockquote&gt;</content:encoded></item><item><title>Developing custom plugins for the Microsoft's Dev Proxy</title><link>https://www.eliostruyf.com/developing-custom-plugins-microsoft-dev-proxy/</link><pubDate>Thu, 28 Mar 2024 10:13:29 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/developing-custom-plugins-microsoft-dev-proxy/</guid><description>For a training project I was working on, I needed to be able to intercept some API calls for some audit logging. To do this, I decided to use Microsoft&amp;rsquo;s Dev Proxy tool, which you use to simulate, mock, and test APIs.
  622. As the Dev Proxy did not have the functionality I needed out of the box, I decided to develop a custom plugin with the help of Waldek Mastykarz.</description><content:encoded>&lt;p&gt;For a training project I was working on, I needed to be able to intercept some API calls for some audit logging. To do this, I decided to use &lt;a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/overview"&gt;Microsoft&amp;rsquo;s Dev Proxy&lt;/a&gt; tool, which you use to simulate, mock, and test APIs.&lt;/p&gt;
  623. &lt;p&gt;As the Dev Proxy did not have the functionality I needed out of the box, I decided to develop a custom plugin with the help of &lt;a href="https://blog.mastykarz.nl/"&gt;Waldek Mastykarz&lt;/a&gt;.&lt;/p&gt;
  624. &lt;p&gt;In this blog post, I will show you how you can develop custom plugins for the Dev Proxy.&lt;/p&gt;
  625. &lt;h2 id="things-you-need-to-get-started"&gt;Things you need to get started&lt;/h2&gt;
  626. &lt;p&gt;Before you can start developing custom plugins for the Dev Proxy, you need to have the following tools installed:&lt;/p&gt;
  627. &lt;ul&gt;
  628. &lt;li&gt;&lt;a href="https://dotnet.microsoft.com/download"&gt;.NET Core SDK&lt;/a&gt;&lt;/li&gt;
  629. &lt;li&gt;&lt;a href="https://code.visualstudio.com/"&gt;Visual Studio Code&lt;/a&gt;&lt;/li&gt;
  630. &lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit"&gt;C# Dev Kit Extension&lt;/a&gt;&lt;/li&gt;
  631. &lt;li&gt;The Dev Proxy Abstractions DLL&lt;/li&gt;
  632. &lt;/ul&gt;
  633. &lt;blockquote class='important'&gt;
  634. &lt;div class='mb-2'&gt;
  635. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  636. &lt;/div&gt;
  637. &lt;p&gt;
  638. &lt;span&gt;The Dev Proxy Abstractions DLL is available on the &lt;a href="https://github.com/microsoft/dev-proxy/releases"&gt;Microsoft&amp;rsquo;s Dev Proxy GitHub releases&lt;/a&gt; page.&lt;/span&gt;
  639. &lt;/p&gt;
  640. &lt;/blockquote&gt;
  641. &lt;div class="caption my-4"&gt;
  642. &lt;figure class="caption__figure"&gt;
  643. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/devproxy-abstractions.webp" title="Show image"&gt;
  644. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  645. &lt;img data-lqip="data:image/jpeg;base64,UklGRnQAAABXRUJQVlA4WAoAAAAQAAAACQAAAwAAQUxQSCEAAAABH0AgadM/6PaXOK8REXFWqAlAgLFu/TNQOKL/8ZUIka8AVlA4ICwAAABwAQCdASoKAAQAAUAmJZwCdAFAAAD&amp;#43;/FN68yIkZuMmDyv3&amp;#43;4IuZgJk0eAAAA==" src="https://www.eliostruyf.com/uploads/2024/03/devproxy-abstractions.webp" alt="Dev Proxy Abstractions DLL" style="width: 1896px;" class="lazyload" /&gt;
  646. &lt;/a&gt;
  647. &lt;figcaption class="caption__text"&gt;Dev Proxy Abstractions DLL&lt;/figcaption&gt;
  648. &lt;/figure&gt;
  649. &lt;/div&gt;
  650. &lt;h2 id="setting-up-the-project"&gt;Setting up the project&lt;/h2&gt;
  651. &lt;p&gt;Once you install all the prerequisites, you can start setting up the project.&lt;/p&gt;
  652. &lt;ul&gt;
  653. &lt;li&gt;Start creating the new project by running the following command in your terminal:&lt;/li&gt;
  654. &lt;/ul&gt;
  655. &lt;figure class="codeblock_titled "&gt;
  656. &lt;figcaption class="codeblock_titled__header"&gt;Create a new class library project&lt;/figcaption&gt;
  657. &lt;div class="highlight" title="Create a new class library project"&gt;&lt;div class="chroma"&gt;
  658. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  659. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  660. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  661. &lt;td class="lntd"&gt;
  662. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet new classlib -n DevProxyCustomPlugin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  663. &lt;/div&gt;
  664. &lt;/div&gt;
  665. &lt;/figure&gt;
  666. &lt;ul&gt;
  667. &lt;li&gt;Next, navigate to the newly created project folder:&lt;/li&gt;
  668. &lt;/ul&gt;
  669. &lt;figure class="codeblock_titled "&gt;
  670. &lt;figcaption class="codeblock_titled__header"&gt;Open the project folder&lt;/figcaption&gt;
  671. &lt;div class="highlight" title="Open the project folder"&gt;&lt;div class="chroma"&gt;
  672. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  673. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  674. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  675. &lt;td class="lntd"&gt;
  676. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; DevProxyCustomPlugin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  677. &lt;/div&gt;
  678. &lt;/div&gt;
  679. &lt;/figure&gt;
  680. &lt;ul&gt;
  681. &lt;li&gt;Open the project in Visual Studio Code&lt;/li&gt;
  682. &lt;li&gt;Add the Dev Proxy Abstractions DLL to the project&lt;/li&gt;
  683. &lt;li&gt;Open the &lt;code&gt;DevProxyCustomPlugin.csproj&lt;/code&gt; file and add the following lines in the project group:&lt;/li&gt;
  684. &lt;/ul&gt;
  685. &lt;figure class="codeblock_titled "&gt;
  686. &lt;figcaption class="codeblock_titled__header"&gt;Add the Dev Proxy Abstractions DLL reference for the project&lt;/figcaption&gt;
  687. &lt;div class="highlight" title="Add the Dev Proxy Abstractions DLL reference for the project"&gt;&lt;div class="chroma"&gt;
  688. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  689. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  690. &lt;/span&gt;&lt;span class="lnt"&gt;2
  691. &lt;/span&gt;&lt;span class="lnt"&gt;3
  692. &lt;/span&gt;&lt;span class="lnt"&gt;4
  693. &lt;/span&gt;&lt;span class="lnt"&gt;5
  694. &lt;/span&gt;&lt;span class="lnt"&gt;6
  695. &lt;/span&gt;&lt;span class="lnt"&gt;7
  696. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  697. &lt;td class="lntd"&gt;
  698. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
  699. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;dev-proxy-abstractions&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  700. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;HintPath&amp;gt;&lt;/span&gt;.\dev-proxy-abstractions.dll&lt;span class="nt"&gt;&amp;lt;/HintPath&amp;gt;&lt;/span&gt;
  701. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;Private&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/Private&amp;gt;&lt;/span&gt;
  702. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ExcludeAssets&amp;gt;&lt;/span&gt;runtime&lt;span class="nt"&gt;&amp;lt;/ExcludeAssets&amp;gt;&lt;/span&gt;
  703. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/Reference&amp;gt;&lt;/span&gt;
  704. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  705. &lt;/div&gt;
  706. &lt;/div&gt;
  707. &lt;/figure&gt;
  708. &lt;blockquote class='important'&gt;
  709. &lt;div class='mb-2'&gt;
  710. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  711. &lt;/div&gt;
  712. &lt;p&gt;
  713. &lt;span&gt;Make sure to exclude the DLL as it is not requires for the plugin. You can do this by setting the &lt;code&gt;ExcludeAssets&lt;/code&gt; to &lt;code&gt;runtime&lt;/code&gt;.&lt;/span&gt;
  714. &lt;/p&gt;
  715. &lt;/blockquote&gt;
  716. &lt;ul&gt;
  717. &lt;li&gt;Add the required packages:&lt;/li&gt;
  718. &lt;/ul&gt;
  719. &lt;figure class="codeblock_titled "&gt;
  720. &lt;figcaption class="codeblock_titled__header"&gt;Add the required packages&lt;/figcaption&gt;
  721. &lt;div class="highlight" title="Add the required packages"&gt;&lt;div class="chroma"&gt;
  722. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  723. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  724. &lt;/span&gt;&lt;span class="lnt"&gt;2
  725. &lt;/span&gt;&lt;span class="lnt"&gt;3
  726. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  727. &lt;td class="lntd"&gt;
  728. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet add package Microsoft.Extensions.Configuration
  729. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet add package Microsoft.Extensions.Configuration.Binder
  730. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet add package Titanium.Web.Proxy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  731. &lt;/div&gt;
  732. &lt;/div&gt;
  733. &lt;/figure&gt;
  734. &lt;ul&gt;
  735. &lt;li&gt;Similar to the &lt;code&gt;dev-proxy-abstractions.dll&lt;/code&gt; configuration, make sure to exclude the new packages&amp;rsquo; assets by adding the &lt;code&gt;ExcludeAssets&lt;/code&gt; property to the &lt;code&gt;PackageReference&lt;/code&gt; element in the &lt;code&gt;DevProxyCustomPlugin.csproj&lt;/code&gt; file:&lt;/li&gt;
  736. &lt;/ul&gt;
  737. &lt;figure class="codeblock_titled "&gt;
  738. &lt;figcaption class="codeblock_titled__header"&gt;Exclude the assets for the new packages&lt;/figcaption&gt;
  739. &lt;div class="highlight" title="Exclude the assets for the new packages"&gt;&lt;div class="chroma"&gt;
  740. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  741. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  742. &lt;/span&gt;&lt;span class="lnt"&gt;2
  743. &lt;/span&gt;&lt;span class="lnt"&gt;3
  744. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  745. &lt;td class="lntd"&gt;
  746. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Microsoft.Extensions.Configuration&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;6.0.0&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  747. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ExcludeAssets&amp;gt;&lt;/span&gt;runtime&lt;span class="nt"&gt;&amp;lt;/ExcludeAssets&amp;gt;&lt;/span&gt;
  748. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/PackageReference&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  749. &lt;/div&gt;
  750. &lt;/div&gt;
  751. &lt;/figure&gt;
  752. &lt;ul&gt;
  753. &lt;li&gt;Update the &lt;code&gt;Class1.cs&lt;/code&gt; file with the name of your plugin class:&lt;/li&gt;
  754. &lt;/ul&gt;
  755. &lt;figure class="codeblock_titled "&gt;
  756. &lt;figcaption class="codeblock_titled__header"&gt;Starter code for the plugin class&lt;/figcaption&gt;
  757. &lt;div class="highlight" title="Starter code for the plugin class"&gt;&lt;div class="chroma"&gt;
  758. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  759. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  760. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  761. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  762. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  763. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  764. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  765. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  766. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  767. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  768. &lt;/span&gt;&lt;span class="lnt"&gt;10
  769. &lt;/span&gt;&lt;span class="lnt"&gt;11
  770. &lt;/span&gt;&lt;span class="lnt"&gt;12
  771. &lt;/span&gt;&lt;span class="lnt"&gt;13
  772. &lt;/span&gt;&lt;span class="lnt"&gt;14
  773. &lt;/span&gt;&lt;span class="lnt"&gt;15
  774. &lt;/span&gt;&lt;span class="lnt"&gt;16
  775. &lt;/span&gt;&lt;span class="lnt"&gt;17
  776. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  777. &lt;td class="lntd"&gt;
  778. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.DevProxy.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  779. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  780. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  781. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;DevProxyCustomPlugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  782. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  783. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedirectCalls&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BaseProxyPlugin&lt;/span&gt;
  784. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  785. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RedirectCalls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  786. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  787. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IPluginEvents&lt;/span&gt; &lt;span class="n"&gt;pluginEvents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  788. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;IProxyContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  789. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ISet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UrlToWatch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;urlsToWatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  790. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;IConfigurationSection&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;configSection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  791. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  792. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pluginEvents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlsToWatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configSection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  793. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  794. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  795. &lt;/div&gt;
  796. &lt;/div&gt;
  797. &lt;/figure&gt;
  798. &lt;blockquote class='info'&gt;
  799. &lt;div class='mb-2'&gt;
  800. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  801. &lt;/div&gt;
  802. &lt;p&gt;
  803. &lt;span&gt;For this sample, I created a plugin which you can use to redirect API calls to your local API, which is useful when locally developing a project.&lt;/span&gt;
  804. &lt;/p&gt;
  805. &lt;/blockquote&gt;
  806. &lt;p&gt;With this in place, you can start developing your custom plugin for the Dev Proxy.&lt;/p&gt;
  807. &lt;div class="caption my-4"&gt;
  808. &lt;figure class="caption__figure"&gt;
  809. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/devproxy-custom-plugin.webp" title="Show image"&gt;
  810. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  811. &lt;img data-lqip="data:image/jpeg;base64,UklGRn4AAABXRUJQVlA4WAoAAAAQAAAACQAABQAAQUxQSCMAAAABH0AgadM/5vbXOK8REXFWqAmAhGF40z8D9DRDRP9jDXHPGgBWUDggNAAAABACAJ0BKgoABgABQCYllAJ0AQ8CbxdjOAAA/v54dZUFPTnTrXaLpCju2Z8uQZbbQ0OIAAA=" src="https://www.eliostruyf.com/uploads/2024/03/devproxy-custom-plugin.webp" alt="Start developing your custom plugin" style="width: 1626px;" class="lazyload" /&gt;
  812. &lt;/a&gt;
  813. &lt;figcaption class="caption__text"&gt;Start developing your custom plugin&lt;/figcaption&gt;
  814. &lt;/figure&gt;
  815. &lt;/div&gt;
  816. &lt;h2 id="adding-a-before-request-event-handler"&gt;Adding a before-request event handler&lt;/h2&gt;
  817. &lt;p&gt;You can add a before-request event handler to the plugin to intercept the API calls. In the &lt;code&gt;Register&lt;/code&gt; method, you can add the following code:&lt;/p&gt;
  818. &lt;figure class="codeblock_titled "&gt;
  819. &lt;figcaption class="codeblock_titled__header"&gt;Add a before request event handler&lt;/figcaption&gt;
  820. &lt;div class="highlight" title="Add a before request event handler"&gt;&lt;div class="chroma"&gt;
  821. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  822. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  823. &lt;/span&gt;&lt;span class="lnt"&gt;2
  824. &lt;/span&gt;&lt;span class="lnt"&gt;3
  825. &lt;/span&gt;&lt;span class="lnt"&gt;4
  826. &lt;/span&gt;&lt;span class="lnt"&gt;5
  827. &lt;/span&gt;&lt;span class="lnt"&gt;6
  828. &lt;/span&gt;&lt;span class="lnt"&gt;7
  829. &lt;/span&gt;&lt;span class="lnt"&gt;8
  830. &lt;/span&gt;&lt;span class="lnt"&gt;9
  831. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  832. &lt;td class="lntd"&gt;
  833. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IPluginEvents&lt;/span&gt; &lt;span class="n"&gt;pluginEvents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  834. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;IProxyContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  835. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ISet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UrlToWatch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;urlsToWatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  836. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;IConfigurationSection&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;configSection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  837. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  838. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pluginEvents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlsToWatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configSection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  839. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  840. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pluginEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BeforeRequest&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;OnBeforeRequest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  841. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  842. &lt;/div&gt;
  843. &lt;/div&gt;
  844. &lt;/figure&gt;
  845. &lt;p&gt;Create a new method, &lt;code&gt;OnBeforeRequest&lt;/code&gt; to handle the event:&lt;/p&gt;
  846. &lt;figure class="codeblock_titled "&gt;
  847. &lt;figcaption class="codeblock_titled__header"&gt;Add the OnBeforeRequest method&lt;/figcaption&gt;
  848. &lt;div class="highlight" title="Add the OnBeforeRequest method"&gt;&lt;div class="chroma"&gt;
  849. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  850. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  851. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  852. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  853. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  854. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  855. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  856. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  857. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  858. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  859. &lt;/span&gt;&lt;span class="lnt"&gt;10
  860. &lt;/span&gt;&lt;span class="lnt"&gt;11
  861. &lt;/span&gt;&lt;span class="lnt"&gt;12
  862. &lt;/span&gt;&lt;span class="lnt"&gt;13
  863. &lt;/span&gt;&lt;span class="lnt"&gt;14
  864. &lt;/span&gt;&lt;span class="lnt"&gt;15
  865. &lt;/span&gt;&lt;span class="lnt"&gt;16
  866. &lt;/span&gt;&lt;span class="lnt"&gt;17
  867. &lt;/span&gt;&lt;span class="lnt"&gt;18
  868. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  869. &lt;td class="lntd"&gt;
  870. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;OnBeforeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BeforeRequestEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  871. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  872. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_urlsToWatch&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
  873. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasRequestUrlMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_urlsToWatch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  874. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  875. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// No match for the URL, so we don&amp;#39;t need to do anything&lt;/span&gt;
  876. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  877. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  878. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  879. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Replace the following lines with your custom logic&lt;/span&gt;
  880. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AbsoluteUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://frontmatter.codes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  881. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  882. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AbsoluteUri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  883. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestUri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://frontmatter.codes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;http://localhost:3000&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  884. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  885. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  886. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  887. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  888. &lt;/div&gt;
  889. &lt;/div&gt;
  890. &lt;/figure&gt;
  891. &lt;p&gt;In this example, the &lt;code&gt;OnBeforeRequest&lt;/code&gt; method checks if the request URL contains &lt;code&gt;https://frontmatter.codes&lt;/code&gt;. If it does, it replaces the URL with &lt;code&gt;http://localhost:3000&lt;/code&gt;. That way, I can test the API calls locally while developing the project.&lt;/p&gt;
  892. &lt;blockquote class='info'&gt;
  893. &lt;div class='mb-2'&gt;
  894. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  895. &lt;/div&gt;
  896. &lt;p&gt;
  897. &lt;span&gt;Change the code in the &lt;code&gt;OnBeforeRequest&lt;/code&gt; method to fit your needs.&lt;/span&gt;
  898. &lt;/p&gt;
  899. &lt;/blockquote&gt;
  900. &lt;p&gt;Once you have added the &lt;code&gt;OnBeforeRequest&lt;/code&gt; method&amp;rsquo;s code, you can build the plugin by running the following command in the terminal:&lt;/p&gt;
  901. &lt;figure class="codeblock_titled "&gt;
  902. &lt;figcaption class="codeblock_titled__header"&gt;Build the plugin&lt;/figcaption&gt;
  903. &lt;div class="highlight" title="Build the plugin"&gt;&lt;div class="chroma"&gt;
  904. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  905. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  906. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  907. &lt;td class="lntd"&gt;
  908. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  909. &lt;/div&gt;
  910. &lt;/div&gt;
  911. &lt;/figure&gt;
  912. &lt;h2 id="testing-the-plugin"&gt;Testing the plugin&lt;/h2&gt;
  913. &lt;p&gt;To test the plugin, add it to the Dev Proxy configuration. You can do this by adding the following lines to the &lt;code&gt;devproxyrc.json&lt;/code&gt; file:&lt;/p&gt;
  914. &lt;figure class="codeblock_titled "&gt;
  915. &lt;figcaption class="codeblock_titled__header"&gt;Add the plugin to the Dev Proxy configuration&lt;/figcaption&gt;
  916. &lt;div class="highlight" title="Add the plugin to the Dev Proxy configuration"&gt;&lt;div class="chroma"&gt;
  917. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  918. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  919. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  920. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  921. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  922. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  923. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  924. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  925. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  926. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  927. &lt;/span&gt;&lt;span class="lnt"&gt;10
  928. &lt;/span&gt;&lt;span class="lnt"&gt;11
  929. &lt;/span&gt;&lt;span class="lnt"&gt;12
  930. &lt;/span&gt;&lt;span class="lnt"&gt;13
  931. &lt;/span&gt;&lt;span class="lnt"&gt;14
  932. &lt;/span&gt;&lt;span class="lnt"&gt;15
  933. &lt;/span&gt;&lt;span class="lnt"&gt;16
  934. &lt;/span&gt;&lt;span class="lnt"&gt;17
  935. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  936. &lt;td class="lntd"&gt;
  937. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  938. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;$schema&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/microsoft/dev-proxy/main/schemas/v0.16.0/rc.schema.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  939. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;plugins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
  940. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;RedirectCalls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  941. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;enabled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  942. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pluginPath&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./bin/Debug/net8.0/DevProxyCustomPlugin.dll&amp;#34;&lt;/span&gt;
  943. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  944. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;githubCopilotListener&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  945. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;logPath&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./logs&amp;#34;&lt;/span&gt;
  946. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  947. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;urlsToWatch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  948. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;https://frontmatter.codes/api/*&amp;#34;&lt;/span&gt;
  949. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
  950. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;labelMode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  951. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;logLevel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;debug&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  952. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;newVersionNotification&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stable&amp;#34;&lt;/span&gt;
  953. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  954. &lt;/div&gt;
  955. &lt;/div&gt;
  956. &lt;/figure&gt;
  957. &lt;p&gt;Start the Dev Proxy by running the following command in the terminal:&lt;/p&gt;
  958. &lt;figure class="codeblock_titled "&gt;
  959. &lt;figcaption class="codeblock_titled__header"&gt;Start the Dev Proxy&lt;/figcaption&gt;
  960. &lt;div class="highlight" title="Start the Dev Proxy"&gt;&lt;div class="chroma"&gt;
  961. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  962. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  963. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  964. &lt;td class="lntd"&gt;
  965. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;devproxy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  966. &lt;/div&gt;
  967. &lt;/div&gt;
  968. &lt;/figure&gt;
  969. &lt;p&gt;Now, you can test the plugin by requesting the URL specified in the &lt;code&gt;urlsToWatch&lt;/code&gt; configuration. In my example, when I call the &lt;code&gt;https://frontmatter.codes/api/stars&lt;/code&gt; API, the request gets redirected to &lt;code&gt;http://localhost:5000/api/stars&lt;/code&gt;, and I can see the response from my local API.&lt;/p&gt;
  970. &lt;div class="caption my-4"&gt;
  971. &lt;figure class="caption__figure"&gt;
  972. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/devproxy-redirect.webp" title="Show image"&gt;
  973. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  974. &lt;img data-lqip="data:image/jpeg;base64,UklGRmQAAABXRUJQVlA4WAoAAAAQAAAACQAAAQAAQUxQSBMAAAABD0CQbeOvuO3niojgWIGI/ocCAFZQOCAqAAAA0AEAnQEqCgACAAFAJiWUAnQBDv4DyAAA/v1Xo4i0ByPhQySESbaqZwAA" src="https://www.eliostruyf.com/uploads/2024/03/devproxy-redirect.webp" alt="Test the plugin by making a request" style="width: 1302px;" class="lazyload" /&gt;
  975. &lt;/a&gt;
  976. &lt;figcaption class="caption__text"&gt;Test the plugin by making a request&lt;/figcaption&gt;
  977. &lt;/figure&gt;
  978. &lt;/div&gt;
  979. &lt;h2 id="adding-some-plugin-configuration"&gt;Adding some plugin configuration&lt;/h2&gt;
  980. &lt;p&gt;To make the plugin more flexible, you can add some configuration options. For instance, you can define the root URL and the local URL to redirect the API calls to.&lt;/p&gt;
  981. &lt;p&gt;To do this, you can add the following configuration to the &lt;code&gt;devproxyrc.json&lt;/code&gt; file:&lt;/p&gt;
  982. &lt;figure class="codeblock_titled "&gt;
  983. &lt;figcaption class="codeblock_titled__header"&gt;Add the plugin configuration to the Dev Proxy configuration&lt;/figcaption&gt;
  984. &lt;div class="highlight" title="Add the plugin configuration to the Dev Proxy configuration"&gt;&lt;div class="chroma"&gt;
  985. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  986. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  987. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  988. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  989. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  990. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  991. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  992. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  993. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  994. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  995. &lt;/span&gt;&lt;span class="lnt"&gt;10
  996. &lt;/span&gt;&lt;span class="lnt"&gt;11
  997. &lt;/span&gt;&lt;span class="lnt"&gt;12
  998. &lt;/span&gt;&lt;span class="lnt"&gt;13
  999. &lt;/span&gt;&lt;span class="lnt"&gt;14
  1000. &lt;/span&gt;&lt;span class="lnt"&gt;15
  1001. &lt;/span&gt;&lt;span class="lnt"&gt;16
  1002. &lt;/span&gt;&lt;span class="lnt"&gt;17
  1003. &lt;/span&gt;&lt;span class="lnt"&gt;18
  1004. &lt;/span&gt;&lt;span class="lnt"&gt;19
  1005. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1006. &lt;td class="lntd"&gt;
  1007. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  1008. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;$schema&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/microsoft/dev-proxy/main/schemas/v0.15.0/rc.schema.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1009. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;plugins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
  1010. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;RedirectCalls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1011. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;enabled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1012. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pluginPath&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./bin/Debug/net8.0/DevProxyCustomPlugin.dll&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1013. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;configSection&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;redirectCalls&amp;#34;&lt;/span&gt;
  1014. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  1015. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;redirectCalls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1016. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;fromUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://frontmatter.codes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1017. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;toUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http://localhost:3000&amp;#34;&lt;/span&gt;
  1018. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  1019. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;urlsToWatch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  1020. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;https://frontmatter.codes/api/*&amp;#34;&lt;/span&gt;
  1021. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
  1022. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;labelMode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1023. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;logLevel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;debug&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1024. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;newVersionNotification&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stable&amp;#34;&lt;/span&gt;
  1025. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1026. &lt;/div&gt;
  1027. &lt;/div&gt;
  1028. &lt;/figure&gt;
  1029. &lt;p&gt;In the &lt;code&gt;RedirectCalls&lt;/code&gt; plugin sections, I added a &lt;code&gt;configSection&lt;/code&gt; property with the value &lt;code&gt;redirectCalls&lt;/code&gt;. This configuration allows me to access the plugin&amp;rsquo;s configuration.&lt;/p&gt;
  1030. &lt;p&gt;In the &lt;code&gt;redirectCalls&lt;/code&gt; section, I added the &lt;code&gt;fromUrl&lt;/code&gt; and &lt;code&gt;toUrl&lt;/code&gt; properties, which define the root URL and the local URL to which the API calls should be redirected.&lt;/p&gt;
  1031. &lt;p&gt;To access the configuration in the plugin, you need to bind the configuration. The whole code for the plugin class should look like this:&lt;/p&gt;
  1032. &lt;figure class="codeblock_titled "&gt;
  1033. &lt;figcaption class="codeblock_titled__header"&gt;Redirect Calls plugin class with configuration&lt;/figcaption&gt;
  1034. &lt;div class="highlight" title="Redirect Calls plugin class with configuration"&gt;&lt;div class="chroma"&gt;
  1035. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1036. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  1037. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  1038. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  1039. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  1040. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  1041. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  1042. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  1043. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  1044. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  1045. &lt;/span&gt;&lt;span class="lnt"&gt;10
  1046. &lt;/span&gt;&lt;span class="lnt"&gt;11
  1047. &lt;/span&gt;&lt;span class="lnt"&gt;12
  1048. &lt;/span&gt;&lt;span class="lnt"&gt;13
  1049. &lt;/span&gt;&lt;span class="lnt"&gt;14
  1050. &lt;/span&gt;&lt;span class="lnt"&gt;15
  1051. &lt;/span&gt;&lt;span class="lnt"&gt;16
  1052. &lt;/span&gt;&lt;span class="lnt"&gt;17
  1053. &lt;/span&gt;&lt;span class="lnt"&gt;18
  1054. &lt;/span&gt;&lt;span class="lnt"&gt;19
  1055. &lt;/span&gt;&lt;span class="lnt"&gt;20
  1056. &lt;/span&gt;&lt;span class="lnt"&gt;21
  1057. &lt;/span&gt;&lt;span class="lnt"&gt;22
  1058. &lt;/span&gt;&lt;span class="lnt"&gt;23
  1059. &lt;/span&gt;&lt;span class="lnt"&gt;24
  1060. &lt;/span&gt;&lt;span class="lnt"&gt;25
  1061. &lt;/span&gt;&lt;span class="lnt"&gt;26
  1062. &lt;/span&gt;&lt;span class="lnt"&gt;27
  1063. &lt;/span&gt;&lt;span class="lnt"&gt;28
  1064. &lt;/span&gt;&lt;span class="lnt"&gt;29
  1065. &lt;/span&gt;&lt;span class="lnt"&gt;30
  1066. &lt;/span&gt;&lt;span class="lnt"&gt;31
  1067. &lt;/span&gt;&lt;span class="lnt"&gt;32
  1068. &lt;/span&gt;&lt;span class="lnt"&gt;33
  1069. &lt;/span&gt;&lt;span class="lnt"&gt;34
  1070. &lt;/span&gt;&lt;span class="lnt"&gt;35
  1071. &lt;/span&gt;&lt;span class="lnt"&gt;36
  1072. &lt;/span&gt;&lt;span class="lnt"&gt;37
  1073. &lt;/span&gt;&lt;span class="lnt"&gt;38
  1074. &lt;/span&gt;&lt;span class="lnt"&gt;39
  1075. &lt;/span&gt;&lt;span class="lnt"&gt;40
  1076. &lt;/span&gt;&lt;span class="lnt"&gt;41
  1077. &lt;/span&gt;&lt;span class="lnt"&gt;42
  1078. &lt;/span&gt;&lt;span class="lnt"&gt;43
  1079. &lt;/span&gt;&lt;span class="lnt"&gt;44
  1080. &lt;/span&gt;&lt;span class="lnt"&gt;45
  1081. &lt;/span&gt;&lt;span class="lnt"&gt;46
  1082. &lt;/span&gt;&lt;span class="lnt"&gt;47
  1083. &lt;/span&gt;&lt;span class="lnt"&gt;48
  1084. &lt;/span&gt;&lt;span class="lnt"&gt;49
  1085. &lt;/span&gt;&lt;span class="lnt"&gt;50
  1086. &lt;/span&gt;&lt;span class="lnt"&gt;51
  1087. &lt;/span&gt;&lt;span class="lnt"&gt;52
  1088. &lt;/span&gt;&lt;span class="lnt"&gt;53
  1089. &lt;/span&gt;&lt;span class="lnt"&gt;54
  1090. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1091. &lt;td class="lntd"&gt;
  1092. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.DevProxy.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1093. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1094. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1095. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;DevProxyCustomPlugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1096. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1097. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedirectCallsConfiguration&lt;/span&gt;
  1098. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  1099. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string?&lt;/span&gt; &lt;span class="n"&gt;FromUrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  1100. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string?&lt;/span&gt; &lt;span class="n"&gt;ToUrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  1101. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  1102. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1103. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedirectCalls&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BaseProxyPlugin&lt;/span&gt;
  1104. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  1105. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RedirectCalls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  1106. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;RedirectCallsConfiguration&lt;/span&gt; &lt;span class="n"&gt;_configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  1107. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1108. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IPluginEvents&lt;/span&gt; &lt;span class="n"&gt;pluginEvents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1109. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;IProxyContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1110. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ISet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UrlToWatch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;urlsToWatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1111. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;IConfigurationSection&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;configSection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  1112. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1113. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pluginEvents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlsToWatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configSection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  1114. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1115. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;configSection&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_configuration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  1116. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1117. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pluginEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BeforeRequest&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;OnBeforeRequest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1118. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  1119. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1120. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="n"&gt;OnBeforeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProxyRequestArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  1121. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1122. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_urlsToWatch&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
  1123. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasRequestUrlMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_urlsToWatch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  1124. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1125. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// No match for the URL, so we don&amp;#39;t need to do anything&lt;/span&gt;
  1126. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1127. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  1128. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1129. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fromUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_configuration&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;FromUrl&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1130. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_configuration&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;ToUrl&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1131. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1132. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toUrl&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  1133. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1134. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1135. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  1136. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1137. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AbsoluteUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromUrl&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  1138. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1139. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AbsoluteUri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1140. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestUri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toUrl&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  1141. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  1142. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1143. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1144. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  1145. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1146. &lt;/div&gt;
  1147. &lt;/div&gt;
  1148. &lt;/figure&gt;
  1149. &lt;p&gt;With this configuration in place, you can test the plugin with the new configuration options.&lt;/p&gt;
  1150. &lt;div class="caption my-4"&gt;
  1151. &lt;figure class="caption__figure"&gt;
  1152. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/devproxy-redirect-configuration.webp" title="Show image"&gt;
  1153. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  1154. &lt;img data-lqip="data:image/jpeg;base64,UklGRm4AAABXRUJQVlA4WAoAAAAQAAAACQAABAAAQUxQSCEAAAABFyCQTfxJvyjSiIhYUNC2DdP9DWDLH8xARPQ/xDPkFw8AVlA4ICYAAACwAQCdASoKAAUAAUAmJZwAAxZiFubAAP7&amp;#43;KWvPnWVn4MttPgAAAA==" src="https://www.eliostruyf.com/uploads/2024/03/devproxy-redirect-configuration.webp" alt="Test the plugin with the new configuration options" style="width: 2040px;" class="lazyload" /&gt;
  1155. &lt;/a&gt;
  1156. &lt;figcaption class="caption__text"&gt;Test the plugin with the new configuration options&lt;/figcaption&gt;
  1157. &lt;/figure&gt;
  1158. &lt;/div&gt;
  1159. &lt;p&gt;The example above is available on my &lt;a href="https://github.com/estruyf/devproxy-redirect-plugin"&gt;GitHub devproxy-redirect-plugin repository&lt;/a&gt;.&lt;/p&gt;
  1160. &lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
  1161. &lt;p&gt;Developing custom plugins for Microsoft&amp;rsquo;s Dev Proxy is a great way to extend the tool&amp;rsquo;s functionality. With the help of the Dev Proxy Abstractions DLL, you can easily create custom plugins that can intercept API calls, modify requests and responses, and much more.&lt;/p&gt;</content:encoded></item><item><title>Caching Dev Proxy in your GitHub Actions workflows</title><link>https://www.eliostruyf.com/caching-dev-proxy-github-actions-workflows/</link><pubDate>Thu, 28 Mar 2024 07:16:59 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/caching-dev-proxy-github-actions-workflows/</guid><description>In the previous posts, I explained using the Microsoft&amp;rsquo;s Dev Proxy in a GitHub Actions workflow on a macOS and Ubuntu virtual machine. One thing I noticed is that the Dev Proxy installation fails in some runs.
  1162. Show image Failed to install the Dev Proxy A way to solve this issue is by caching the Dev Proxy, and another benefit is that it speeds up your workflow.</description><content:encoded>&lt;p&gt;In the previous posts, I explained using the &lt;a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/overview"&gt;Microsoft&amp;rsquo;s Dev Proxy&lt;/a&gt; in a GitHub Actions workflow on a macOS and Ubuntu virtual machine. One thing I noticed is that the Dev Proxy installation fails in some runs.&lt;/p&gt;
  1163. &lt;div class="caption my-4"&gt;
  1164. &lt;figure class="caption__figure"&gt;
  1165. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/failed-installing-devproxy.webp" title="Show image"&gt;
  1166. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  1167. &lt;img data-lqip="data:image/jpeg;base64,UklGRmwAAABXRUJQVlA4WAoAAAAQAAAACQAABQAAQUxQSCQAAAABHyAUQPylgunHkEZExBIUtW0DVX11F7yq/MkMQ0T/Iz5CT3xWUDggIgAAALABAJ0BKgoABgABQCYlpAAC6NtvXKAA/v7o1BGKauyXgAA=" src="https://www.eliostruyf.com/uploads/2024/03/failed-installing-devproxy.webp" alt="Failed to install the Dev Proxy" style="width: 900px;" class="lazyload" /&gt;
  1168. &lt;/a&gt;
  1169. &lt;figcaption class="caption__text"&gt;Failed to install the Dev Proxy&lt;/figcaption&gt;
  1170. &lt;/figure&gt;
  1171. &lt;/div&gt;
  1172. &lt;p&gt;A way to solve this issue is by caching the Dev Proxy, and another benefit is that it speeds up your workflow.&lt;/p&gt;
  1173. &lt;p&gt;This blog post shows how to cache the Dev Proxy in your GitHub Actions workflows. By caching it, it uses the cached version if it is available, and if not, it will download and install it.&lt;/p&gt;
  1174. &lt;h2 id="retrieving-the-latest-dev-proxy-version-number"&gt;Retrieving the latest Dev Proxy version number&lt;/h2&gt;
  1175. &lt;p&gt;We must first fetch the latest version number to cache the Dev Proxy. That way, we only retrieve the latest version if it is not cached. For this, we can use the GitHub release API:&lt;/p&gt;
  1176. &lt;figure class="codeblock_titled "&gt;
  1177. &lt;figcaption class="codeblock_titled__header"&gt;Get the latest Dev Proxy released version number&lt;/figcaption&gt;
  1178. &lt;div class="highlight" title="Get the latest Dev Proxy released version number"&gt;&lt;div class="chroma"&gt;
  1179. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1180. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  1181. &lt;/span&gt;&lt;span class="lnt"&gt;2
  1182. &lt;/span&gt;&lt;span class="lnt"&gt;3
  1183. &lt;/span&gt;&lt;span class="lnt"&gt;4
  1184. &lt;/span&gt;&lt;span class="lnt"&gt;5
  1185. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1186. &lt;td class="lntd"&gt;
  1187. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- name: Store Dev Proxy&lt;span class="s1"&gt;&amp;#39;s Version
  1188. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; run: |
  1189. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; DEVPROXY_VERSION=$(curl -s https://api.github.com/repos/microsoft/dev-proxy/releases/latest | jq .tag_name -r)
  1190. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; echo &amp;#34;Dev Proxy&amp;#39;&lt;/span&gt;s Version: &lt;span class="nv"&gt;$DEVPROXY_VERSION&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;
  1191. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; echo &amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;DEVPROXY_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$DEVPROXY_VERSION&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_ENV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1192. &lt;/div&gt;
  1193. &lt;/div&gt;
  1194. &lt;/figure&gt;
  1195. &lt;p&gt;In the above step, we use &lt;code&gt;curl&lt;/code&gt; to fetch the latest release information from the Dev Proxy repository. We use &lt;code&gt;jq&lt;/code&gt; to extract the &lt;code&gt;tag_name&lt;/code&gt; from the JSON response. The version number gets stored in the &lt;code&gt;DEVPROXY_VERSION&lt;/code&gt; environment variable.&lt;/p&gt;
  1196. &lt;h2 id="caching-the-dev-proxy"&gt;Caching the Dev Proxy&lt;/h2&gt;
  1197. &lt;p&gt;Next, we use the &lt;a href="https://github.com/marketplace/actions/cache"&gt;actions/cache&lt;/a&gt; action to cache the Dev Proxy. We can use the following step to cache the Dev Proxy:&lt;/p&gt;
  1198. &lt;figure class="codeblock_titled "&gt;
  1199. &lt;figcaption class="codeblock_titled__header"&gt;Cache Dev Proxy - GitHub Actions step&lt;/figcaption&gt;
  1200. &lt;div class="highlight" title="Cache Dev Proxy - GitHub Actions step"&gt;&lt;div class="chroma"&gt;
  1201. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1202. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  1203. &lt;/span&gt;&lt;span class="lnt"&gt;2
  1204. &lt;/span&gt;&lt;span class="lnt"&gt;3
  1205. &lt;/span&gt;&lt;span class="lnt"&gt;4
  1206. &lt;/span&gt;&lt;span class="lnt"&gt;5
  1207. &lt;/span&gt;&lt;span class="lnt"&gt;6
  1208. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1209. &lt;td class="lntd"&gt;
  1210. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Cache Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1211. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cache-devproxy&lt;/span&gt;&lt;span class="w"&gt;
  1212. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/cache@v4&lt;/span&gt;&lt;span class="w"&gt;
  1213. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1214. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy&lt;/span&gt;&lt;span class="w"&gt;
  1215. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;devproxy-${{ env.DEVPROXY_VERSION }}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1216. &lt;/div&gt;
  1217. &lt;/div&gt;
  1218. &lt;/figure&gt;
  1219. &lt;p&gt;In the above step, we use the &lt;code&gt;actions/cache&lt;/code&gt; action to cache the &lt;code&gt;devproxy&lt;/code&gt; folder. We use the &lt;code&gt;DEVPROXY_VERSION&lt;/code&gt; environment variable as the key for the cache so that when a new version is released, the latest version will be installed and cached.&lt;/p&gt;
  1220. &lt;p&gt;The cache action outputs a &lt;code&gt;cache-hit&lt;/code&gt; boolean to indicate whether the Dev Proxy version was cached. The next steps will use that output to determine whether the Dev Proxy needs to be installed.&lt;/p&gt;
  1221. &lt;h2 id="installing-the-dev-proxy"&gt;Installing the Dev Proxy&lt;/h2&gt;
  1222. &lt;p&gt;After the cache action, we can use the following step to install the Dev Proxy only when needed:&lt;/p&gt;
  1223. &lt;figure class="codeblock_titled "&gt;
  1224. &lt;figcaption class="codeblock_titled__header"&gt;Install Dev Proxy - GitHub Actions step&lt;/figcaption&gt;
  1225. &lt;div class="highlight" title="Install Dev Proxy - GitHub Actions step"&gt;&lt;div class="chroma"&gt;
  1226. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1227. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  1228. &lt;/span&gt;&lt;span class="lnt"&gt;2
  1229. &lt;/span&gt;&lt;span class="lnt"&gt;3
  1230. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1231. &lt;td class="lntd"&gt;
  1232. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1233. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;steps.cache-devproxy.outputs.cache-hit != &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
  1234. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash -c &amp;#34;$(curl -sL https://aka.ms/devproxy/setup.sh)&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1235. &lt;/div&gt;
  1236. &lt;/div&gt;
  1237. &lt;/figure&gt;
  1238. &lt;p&gt;In the above step, we use the &lt;code&gt;if&lt;/code&gt; condition to check if the Dev Proxy version was available from the cache. Notice the &lt;code&gt;cache-devproxy&lt;/code&gt; and &lt;code&gt;cache-hit&lt;/code&gt; references. The &lt;code&gt;cache-devproxy&lt;/code&gt; is the ID of the cache action, and &lt;code&gt;cache-hit&lt;/code&gt; is the output of the cache action.&lt;/p&gt;
  1239. &lt;p&gt;Using this approach, you can cache the Dev Proxy in your GitHub Actions workflows and speed up your workflow.&lt;/p&gt;
  1240. &lt;div class="caption my-4"&gt;
  1241. &lt;figure class="caption__figure"&gt;
  1242. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/using-cached-devproxy.webp" title="Show image"&gt;
  1243. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  1244. &lt;img data-lqip="data:image/jpeg;base64,UklGRm4AAABXRUJQVlA4WAoAAAAQAAAACQAABQAAQUxQSCoAAAABJ0CQbTOASU76OteIiOBLGASYJoVucIcMmr/UDxHR/wnQLZCxvU&amp;#43;B6AJWUDggHgAAADABAJ0BKgoABgABQCYlpAADcAD&amp;#43;/RsYm5efeOAAAA==" src="https://www.eliostruyf.com/uploads/2024/03/using-cached-devproxy.webp" alt="Using the cached Dev Proxy" style="width: 1568px;" class="lazyload" /&gt;
  1245. &lt;/a&gt;
  1246. &lt;figcaption class="caption__text"&gt;Using the cached Dev Proxy&lt;/figcaption&gt;
  1247. &lt;/figure&gt;
  1248. &lt;/div&gt;
  1249. &lt;h2 id="the-complete-github-actions-workflow"&gt;The complete GitHub Actions workflow&lt;/h2&gt;
  1250. &lt;p&gt;Here is the complete GitHub Actions workflow that caches the Dev Proxy:&lt;/p&gt;
  1251. &lt;figure class="codeblock_titled "&gt;
  1252. &lt;figcaption class="codeblock_titled__header"&gt;Complete GitHub Actions workflow&lt;/figcaption&gt;
  1253. &lt;div class="highlight" title="Complete GitHub Actions workflow"&gt;&lt;div class="chroma"&gt;
  1254. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1255. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  1256. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  1257. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  1258. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  1259. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  1260. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  1261. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  1262. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  1263. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  1264. &lt;/span&gt;&lt;span class="lnt"&gt;10
  1265. &lt;/span&gt;&lt;span class="lnt"&gt;11
  1266. &lt;/span&gt;&lt;span class="lnt"&gt;12
  1267. &lt;/span&gt;&lt;span class="lnt"&gt;13
  1268. &lt;/span&gt;&lt;span class="lnt"&gt;14
  1269. &lt;/span&gt;&lt;span class="lnt"&gt;15
  1270. &lt;/span&gt;&lt;span class="lnt"&gt;16
  1271. &lt;/span&gt;&lt;span class="lnt"&gt;17
  1272. &lt;/span&gt;&lt;span class="lnt"&gt;18
  1273. &lt;/span&gt;&lt;span class="lnt"&gt;19
  1274. &lt;/span&gt;&lt;span class="lnt"&gt;20
  1275. &lt;/span&gt;&lt;span class="lnt"&gt;21
  1276. &lt;/span&gt;&lt;span class="lnt"&gt;22
  1277. &lt;/span&gt;&lt;span class="lnt"&gt;23
  1278. &lt;/span&gt;&lt;span class="lnt"&gt;24
  1279. &lt;/span&gt;&lt;span class="lnt"&gt;25
  1280. &lt;/span&gt;&lt;span class="lnt"&gt;26
  1281. &lt;/span&gt;&lt;span class="lnt"&gt;27
  1282. &lt;/span&gt;&lt;span class="lnt"&gt;28
  1283. &lt;/span&gt;&lt;span class="lnt"&gt;29
  1284. &lt;/span&gt;&lt;span class="lnt"&gt;30
  1285. &lt;/span&gt;&lt;span class="lnt"&gt;31
  1286. &lt;/span&gt;&lt;span class="lnt"&gt;32
  1287. &lt;/span&gt;&lt;span class="lnt"&gt;33
  1288. &lt;/span&gt;&lt;span class="lnt"&gt;34
  1289. &lt;/span&gt;&lt;span class="lnt"&gt;35
  1290. &lt;/span&gt;&lt;span class="lnt"&gt;36
  1291. &lt;/span&gt;&lt;span class="lnt"&gt;37
  1292. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1293. &lt;td class="lntd"&gt;
  1294. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1295. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1296. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1297. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1298. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1299. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
  1300. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
  1301. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1302. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1303. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1304. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1305. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
  1306. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
  1307. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1308. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
  1309. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1310. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Store Dev Proxy&amp;#39;s Version&lt;/span&gt;&lt;span class="w"&gt;
  1311. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  1312. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; DEVPROXY_VERSION=$(curl -s https://api.github.com/repos/microsoft/dev-proxy/releases/latest | jq .tag_name -r)
  1313. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Dev Proxy&amp;#39;s Version: $DEVPROXY_VERSION&amp;#34;
  1314. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;DEVPROXY_VERSION=$DEVPROXY_VERSION&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;
  1315. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1316. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Cache Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1317. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cache-devproxy&lt;/span&gt;&lt;span class="w"&gt;
  1318. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/cache@v4&lt;/span&gt;&lt;span class="w"&gt;
  1319. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1320. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy&lt;/span&gt;&lt;span class="w"&gt;
  1321. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;devproxy-${{ env.DEVPROXY_VERSION }}&lt;/span&gt;&lt;span class="w"&gt;
  1322. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1323. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1324. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;steps.cache-devproxy.outputs.cache-hit != &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
  1325. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash -c &amp;#34;$(curl -sL https://aka.ms/devproxy/setup.sh)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  1326. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1327. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1328. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy/devproxy &amp;amp;&lt;/span&gt;&lt;span class="w"&gt;
  1329. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1330. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Include all the other steps to start using the Dev Proxy&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1331. &lt;/div&gt;
  1332. &lt;/div&gt;
  1333. &lt;/figure&gt;
  1334. &lt;p&gt;In the above workflow, we first store the Dev Proxy&amp;rsquo;s version number. We then cache the Dev Proxy using the &lt;code&gt;actions/cache&lt;/code&gt; action. If the Dev Proxy version was not cached, we install it using the &lt;code&gt;curl&lt;/code&gt; command. Finally, we run the Dev Proxy using the &lt;code&gt;./devproxy/devproxy&lt;/code&gt; command.&lt;/p&gt;
  1335. &lt;blockquote class='info'&gt;
  1336. &lt;div class='mb-2'&gt;
  1337. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1338. &lt;/div&gt;
  1339. &lt;p&gt;
  1340. &lt;span&gt;Templates are available on the following &lt;a href="https://github.com/estruyf/devproxy-github-actions-templates"&gt;GitHub repository&lt;/a&gt;.&lt;/span&gt;
  1341. &lt;/p&gt;
  1342. &lt;/blockquote&gt;</content:encoded></item><item><title>Using Dev Proxy in your GitHub Actions workflow on Ubuntu</title><link>https://www.eliostruyf.com/dev-proxy-github-actions-workflow-ubuntu/</link><pubDate>Wed, 27 Mar 2024 08:25:54 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/dev-proxy-github-actions-workflow-ubuntu/</guid><description>In my previous blog post, I explained how you could use the Microsoft&amp;rsquo;s Dev Proxy in a GitHub Actions workflow on a macOS runner. In this blog post, I will show you how to use the Dev Proxy in your GitHub Actions workflow on an Ubuntu runner.
  1343. info You can read how to configure it on a macOS runner in the Using Dev Proxy in your GitHub Actions workflow on a macOS runner article.</description><content:encoded>&lt;p&gt;In my previous blog post, I explained how you could use the &lt;a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/overview"&gt;Microsoft&amp;rsquo;s Dev Proxy&lt;/a&gt; in a GitHub Actions workflow on a macOS runner. In this blog post, I will show you how to use the Dev Proxy in your GitHub Actions workflow on an Ubuntu runner.&lt;/p&gt;
  1344. &lt;blockquote class='info'&gt;
  1345. &lt;div class='mb-2'&gt;
  1346. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1347. &lt;/div&gt;
  1348. &lt;p&gt;
  1349. &lt;span&gt;You can read how to configure it on a macOS runner in the &lt;a href="https://www.eliostruyf.com/dev-proxy-github-actions-workflow-macos/"&gt;Using Dev Proxy in your GitHub Actions workflow on a macOS runner&lt;/a&gt; article.&lt;/span&gt;
  1350. &lt;/p&gt;
  1351. &lt;/blockquote&gt;
  1352. &lt;p&gt;Most of the steps are the same, except how you trust the root certificate.&lt;/p&gt;
  1353. &lt;h2 id="installing-and-running-the-dev-proxy"&gt;Installing and running the Dev Proxy&lt;/h2&gt;
  1354. &lt;p&gt;Like the macOS runner, you can install the bash script provided in the Dev Proxy documentation on the Ubuntu runner. To include this into your GitHub Actions workflow, you can use the following step:&lt;/p&gt;
  1355. &lt;figure class="codeblock_titled "&gt;
  1356. &lt;figcaption class="codeblock_titled__header"&gt;Install and run the Dev Proxy - GitHub Actions steps&lt;/figcaption&gt;
  1357. &lt;div class="highlight" title="Install and run the Dev Proxy - GitHub Actions steps"&gt;&lt;div class="chroma"&gt;
  1358. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1359. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  1360. &lt;/span&gt;&lt;span class="lnt"&gt;2
  1361. &lt;/span&gt;&lt;span class="lnt"&gt;3
  1362. &lt;/span&gt;&lt;span class="lnt"&gt;4
  1363. &lt;/span&gt;&lt;span class="lnt"&gt;5
  1364. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1365. &lt;td class="lntd"&gt;
  1366. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1367. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash -c &amp;#34;$(curl -sL https://aka.ms/devproxy/setup.sh)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  1368. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1369. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1370. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy/devproxy &amp;amp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1371. &lt;/div&gt;
  1372. &lt;/div&gt;
  1373. &lt;/figure&gt;
  1374. &lt;blockquote class='info'&gt;
  1375. &lt;div class='mb-2'&gt;
  1376. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1377. &lt;/div&gt;
  1378. &lt;p&gt;
  1379. &lt;span&gt;The Dev Proxy commands is using an ampersand &lt;code&gt;&amp;amp;&lt;/code&gt; to run it as a background service. You can read more about it in the &lt;a href="https://www.eliostruyf.com/devhack-running-background-service-github-actions/"&gt;#DevHack: Running a background service on GitHub Actions&lt;/a&gt; article.&lt;/span&gt;
  1380. &lt;/p&gt;
  1381. &lt;/blockquote&gt;
  1382. &lt;p&gt;Once the Dev Proxy is installed, you can run it, but you cannot yet intercept HTTPS traffic. That is where the next step comes in. You need to trust the root certificate.&lt;/p&gt;
  1383. &lt;h2 id="trust-the-root-certificate"&gt;Trust the root certificate&lt;/h2&gt;
  1384. &lt;p&gt;Similar to the macOS configuration, we must trust the self-signed certificate the Dev Proxy created. Here are the steps to achieve the certificate trust on an Ubuntu runner:&lt;/p&gt;
  1385. &lt;figure class="codeblock_titled "&gt;
  1386. &lt;figcaption class="codeblock_titled__header"&gt;Run Dev Proxy - GitHub Actions step&lt;/figcaption&gt;
  1387. &lt;div class="highlight" title="Run Dev Proxy - GitHub Actions step"&gt;&lt;div class="chroma"&gt;
  1388. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1389. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  1390. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  1391. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  1392. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  1393. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  1394. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  1395. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  1396. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  1397. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  1398. &lt;/span&gt;&lt;span class="lnt"&gt;10
  1399. &lt;/span&gt;&lt;span class="lnt"&gt;11
  1400. &lt;/span&gt;&lt;span class="lnt"&gt;12
  1401. &lt;/span&gt;&lt;span class="lnt"&gt;13
  1402. &lt;/span&gt;&lt;span class="lnt"&gt;14
  1403. &lt;/span&gt;&lt;span class="lnt"&gt;15
  1404. &lt;/span&gt;&lt;span class="lnt"&gt;16
  1405. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1406. &lt;td class="lntd"&gt;
  1407. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install the Dev Proxy&amp;#39;s certificate&lt;/span&gt;&lt;span class="w"&gt;
  1408. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  1409. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  1410. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Export the Dev Proxy&amp;#39;s Root Certificate&amp;#34;
  1411. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; openssl pkcs12 -in ~/.config/dev-proxy/rootCert.pfx -clcerts -nokeys -out dev-proxy-ca.crt -passin pass:&amp;#34;&amp;#34;
  1412. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1413. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Installing the Dev Proxy&amp;#39;s Root Certificate&amp;#34;
  1414. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo cp dev-proxy-ca.crt /usr/local/share/ca-certificates/
  1415. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1416. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Updating the CA certificates&amp;#34;
  1417. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo update-ca-certificates
  1418. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Certificate trusted.&amp;#34;
  1419. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1420. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Set the system proxy settings (optional)
  1421. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;http_proxy=http://127.0.0.1:8000&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
  1422. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;https_proxy=http://127.0.0.1:8000&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1423. &lt;/div&gt;
  1424. &lt;/div&gt;
  1425. &lt;/figure&gt;
  1426. &lt;blockquote class='info'&gt;
  1427. &lt;div class='mb-2'&gt;
  1428. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1429. &lt;/div&gt;
  1430. &lt;p&gt;
  1431. &lt;span&gt;The commands used in the above GitHub Actions step are very similar to how it is configured on a Docker container. You can find the official documentation for the Docker container in &lt;a href="https://learn.microsoft.com/en-gb/microsoft-cloud/dev/dev-proxy/how-to/use-dev-proxy-with-dotnet-docker?pivots=client-operating-system-windows#configure-dev-proxy-certificate-in-your-docker-container"&gt;Configure Dev Proxy certificate in your Docker container&lt;/a&gt; documentation section.&lt;/span&gt;
  1432. &lt;/p&gt;
  1433. &lt;/blockquote&gt;
  1434. &lt;p&gt;After running this step, you can start intercepting HTTPS traffic with the Dev Proxy.&lt;/p&gt;
  1435. &lt;h2 id="the-complete-github-actions-workflow"&gt;The complete GitHub Actions workflow&lt;/h2&gt;
  1436. &lt;p&gt;Below, you can find the complete GitHub Actions workflow file, which includes the installation of the Dev Proxy and the trust of the root certificate.&lt;/p&gt;
  1437. &lt;figure class="codeblock_titled "&gt;
  1438. &lt;figcaption class="codeblock_titled__header"&gt;Complete GitHub Actions workflow&lt;/figcaption&gt;
  1439. &lt;div class="highlight" title="Complete GitHub Actions workflow"&gt;&lt;div class="chroma"&gt;
  1440. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1441. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  1442. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  1443. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  1444. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  1445. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  1446. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  1447. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  1448. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  1449. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  1450. &lt;/span&gt;&lt;span class="lnt"&gt;10
  1451. &lt;/span&gt;&lt;span class="lnt"&gt;11
  1452. &lt;/span&gt;&lt;span class="lnt"&gt;12
  1453. &lt;/span&gt;&lt;span class="lnt"&gt;13
  1454. &lt;/span&gt;&lt;span class="lnt"&gt;14
  1455. &lt;/span&gt;&lt;span class="lnt"&gt;15
  1456. &lt;/span&gt;&lt;span class="lnt"&gt;16
  1457. &lt;/span&gt;&lt;span class="lnt"&gt;17
  1458. &lt;/span&gt;&lt;span class="lnt"&gt;18
  1459. &lt;/span&gt;&lt;span class="lnt"&gt;19
  1460. &lt;/span&gt;&lt;span class="lnt"&gt;20
  1461. &lt;/span&gt;&lt;span class="lnt"&gt;21
  1462. &lt;/span&gt;&lt;span class="lnt"&gt;22
  1463. &lt;/span&gt;&lt;span class="lnt"&gt;23
  1464. &lt;/span&gt;&lt;span class="lnt"&gt;24
  1465. &lt;/span&gt;&lt;span class="lnt"&gt;25
  1466. &lt;/span&gt;&lt;span class="lnt"&gt;26
  1467. &lt;/span&gt;&lt;span class="lnt"&gt;27
  1468. &lt;/span&gt;&lt;span class="lnt"&gt;28
  1469. &lt;/span&gt;&lt;span class="lnt"&gt;29
  1470. &lt;/span&gt;&lt;span class="lnt"&gt;30
  1471. &lt;/span&gt;&lt;span class="lnt"&gt;31
  1472. &lt;/span&gt;&lt;span class="lnt"&gt;32
  1473. &lt;/span&gt;&lt;span class="lnt"&gt;33
  1474. &lt;/span&gt;&lt;span class="lnt"&gt;34
  1475. &lt;/span&gt;&lt;span class="lnt"&gt;35
  1476. &lt;/span&gt;&lt;span class="lnt"&gt;36
  1477. &lt;/span&gt;&lt;span class="lnt"&gt;37
  1478. &lt;/span&gt;&lt;span class="lnt"&gt;38
  1479. &lt;/span&gt;&lt;span class="lnt"&gt;39
  1480. &lt;/span&gt;&lt;span class="lnt"&gt;40
  1481. &lt;/span&gt;&lt;span class="lnt"&gt;41
  1482. &lt;/span&gt;&lt;span class="lnt"&gt;42
  1483. &lt;/span&gt;&lt;span class="lnt"&gt;43
  1484. &lt;/span&gt;&lt;span class="lnt"&gt;44
  1485. &lt;/span&gt;&lt;span class="lnt"&gt;45
  1486. &lt;/span&gt;&lt;span class="lnt"&gt;46
  1487. &lt;/span&gt;&lt;span class="lnt"&gt;47
  1488. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1489. &lt;td class="lntd"&gt;
  1490. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Ubuntu Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1491. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1492. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1493. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1494. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1495. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
  1496. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
  1497. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1498. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1499. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1500. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1501. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
  1502. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
  1503. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1504. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
  1505. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1506. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-node@v4&lt;/span&gt;&lt;span class="w"&gt;
  1507. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1508. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1509. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash -c &amp;#34;$(curl -sL https://aka.ms/devproxy/setup.sh)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  1510. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1511. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1512. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy/devproxy &amp;amp;&lt;/span&gt;&lt;span class="w"&gt;
  1513. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1514. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install the Dev Proxy&amp;#39;s certificate&lt;/span&gt;&lt;span class="w"&gt;
  1515. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  1516. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  1517. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Export the Dev Proxy&amp;#39;s Root Certificate&amp;#34;
  1518. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; openssl pkcs12 -in ~/.config/dev-proxy/rootCert.pfx -clcerts -nokeys -out dev-proxy-ca.crt -passin pass:&amp;#34;&amp;#34;
  1519. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1520. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Installing the Dev Proxy&amp;#39;s Root Certificate&amp;#34;
  1521. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo cp dev-proxy-ca.crt /usr/local/share/ca-certificates/
  1522. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1523. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Updating the CA certificates&amp;#34;
  1524. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo update-ca-certificates
  1525. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Certificate trusted.&amp;#34;
  1526. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1527. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Set the system proxy settings (optional)
  1528. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;http_proxy=http://127.0.0.1:8000&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
  1529. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;https_proxy=http://127.0.0.1:8000&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;
  1530. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1531. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Include the additional steps you want to run after the Dev Proxy started&lt;/span&gt;&lt;span class="w"&gt;
  1532. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Test the Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1533. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  1534. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -ix http://127.0.0.1:8000 https://jsonplaceholder.typicode.com/posts
  1535. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # When you used the system proxy settings, you don&amp;#39;t need to specify the proxy in the curl command
  1536. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -i https://jsonplaceholder.typicode.com/posts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1537. &lt;/div&gt;
  1538. &lt;/div&gt;
  1539. &lt;/figure&gt;
  1540. &lt;div class="caption my-4"&gt;
  1541. &lt;figure class="caption__figure"&gt;
  1542. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/devproxy-with-trusted-root-certificate.webp" title="Show image"&gt;
  1543. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  1544. &lt;img data-lqip="data:image/jpeg;base64,UklGRnQAAABXRUJQVlA4WAoAAAAQAAAACQAABgAAQUxQSCQAAAABH6CQbQTIn3L3eNxpREScFWoCIGEY3vTPAD3NENH/WEPcvwZWUDggKgAAAPABAJ0BKgoABwABQCYlnALsAQN9qaGwgAD&amp;#43;/tQofIXyVWeusWS/egAAAA==" src="https://www.eliostruyf.com/uploads/2024/03/devproxy-with-trusted-root-certificate.webp" alt="Test Dev Proxy on GitHub Actions" style="width: 900px;" class="lazyload" /&gt;
  1545. &lt;/a&gt;
  1546. &lt;figcaption class="caption__text"&gt;Test Dev Proxy on GitHub Actions&lt;/figcaption&gt;
  1547. &lt;/figure&gt;
  1548. &lt;/div&gt;
  1549. &lt;p&gt;With this setup, you can use the Dev Proxy in your GitHub Actions workflow on a Ubuntu runner.&lt;/p&gt;
  1550. &lt;blockquote class='info'&gt;
  1551. &lt;div class='mb-2'&gt;
  1552. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1553. &lt;/div&gt;
  1554. &lt;p&gt;
  1555. &lt;span&gt;Templates are available on the following &lt;a href="https://github.com/estruyf/devproxy-github-actions-templates"&gt;GitHub repository&lt;/a&gt;.&lt;/span&gt;
  1556. &lt;/p&gt;
  1557. &lt;/blockquote&gt;</content:encoded></item><item><title>Using Dev Proxy in your GitHub Actions workflow on macOS</title><link>https://www.eliostruyf.com/dev-proxy-github-actions-workflow-macos/</link><pubDate>Tue, 26 Mar 2024 08:15:17 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/dev-proxy-github-actions-workflow-macos/</guid><description>Lately, I have been working with the Microsoft&amp;rsquo;s Dev Proxy, a tool for API simulation, mocking, and testing. One of the things I wanted to try was to use the Dev Proxy in a GitHub Actions workflow so that I could use it in combination with Playwright to test my solutions with mocked APIs.</description><content:encoded>&lt;p&gt;Lately, I have been working with the &lt;a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/overview"&gt;Microsoft&amp;rsquo;s Dev Proxy&lt;/a&gt;, a tool for API simulation, mocking, and testing. One of the things I wanted to try was to use the Dev Proxy in a GitHub Actions workflow so that I could use it in combination with Playwright to test my solutions with mocked APIs.&lt;/p&gt;
  1558. &lt;p&gt;The Dev Proxy is a .NET Core application that can run on any platform that supports .NET Core, so it works on Windows, Linux, and macOS. I chose to use a macOS virtual machine/runner because, at the time of writing, the Dev Proxy cannot automatically trust the root certificate.&lt;/p&gt;
  1559. &lt;blockquote class='info'&gt;
  1560. &lt;div class='mb-2'&gt;
  1561. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1562. &lt;/div&gt;
  1563. &lt;p&gt;
  1564. &lt;span&gt;Currently there is an issue open on the Dev Proxy GitHub repository to add support for trusting the root certificate on macOS - &lt;a href="https://github.com/microsoft/dev-proxy/issues/601"&gt;Dev Proxy #601&lt;/a&gt;.&lt;/span&gt;
  1565. &lt;/p&gt;
  1566. &lt;/blockquote&gt;
  1567. &lt;p&gt;In this blog post, I will show you how to use the Dev Proxy in your GitHub Actions workflow on a macOS virtual machine.&lt;/p&gt;
  1568. &lt;blockquote class='important'&gt;
  1569. &lt;div class='mb-2'&gt;
  1570. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  1571. &lt;/div&gt;
  1572. &lt;p&gt;
  1573. &lt;span&gt;Be aware of that jobs running on Windows and macOS runners that GitHub hosts consume minutes at 2 and 10 times the rate that jobs on Linux runners consume. You can read more about it in the &lt;a href="https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#minute-multipliers"&gt;GitHub Actions - Minute multipliers for hosted runners&lt;/a&gt; documentation section.&lt;/span&gt;
  1574. &lt;/p&gt;
  1575. &lt;/blockquote&gt;
  1576. &lt;h2 id="installing-the-dev-proxy"&gt;Installing the Dev Proxy&lt;/h2&gt;
  1577. &lt;p&gt;Let us start with installing the Dev Proxy on the macOS virtual machine. We can use the bash script provided in the Dev Proxy documentation for this. To include this into your GitHub Actions workflow, you can use the following step:&lt;/p&gt;
  1578. &lt;figure class="codeblock_titled "&gt;
  1579. &lt;figcaption class="codeblock_titled__header"&gt;Install Dev Proxy - GitHub Actions step&lt;/figcaption&gt;
  1580. &lt;div class="highlight" title="Install Dev Proxy - GitHub Actions step"&gt;&lt;div class="chroma"&gt;
  1581. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1582. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  1583. &lt;/span&gt;&lt;span class="lnt"&gt;2
  1584. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1585. &lt;td class="lntd"&gt;
  1586. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- name: Install Dev Proxy
  1587. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; run: bash -c &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;curl -sL https://aka.ms/devproxy/setup.sh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1588. &lt;/div&gt;
  1589. &lt;/div&gt;
  1590. &lt;/figure&gt;
  1591. &lt;h2 id="running-the-dev-proxy-as-a-background-service"&gt;Running the Dev Proxy as a background service&lt;/h2&gt;
  1592. &lt;p&gt;Once installed, you can run the Dev Proxy as a background service by adding an ampersand &lt;code&gt;&amp;amp;&lt;/code&gt; at the end of the command.&lt;/p&gt;
  1593. &lt;blockquote class='info'&gt;
  1594. &lt;div class='mb-2'&gt;
  1595. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1596. &lt;/div&gt;
  1597. &lt;p&gt;
  1598. &lt;span&gt;You can read more about it in the &lt;a href="https://www.eliostruyf.com/devhack-running-background-service-github-actions/"&gt;#DevHack: Running a background service on GitHub Actions&lt;/a&gt; article.&lt;/span&gt;
  1599. &lt;/p&gt;
  1600. &lt;/blockquote&gt;
  1601. &lt;p&gt;Here is what the GitHub Actions step looks like:&lt;/p&gt;
  1602. &lt;figure class="codeblock_titled "&gt;
  1603. &lt;figcaption class="codeblock_titled__header"&gt;Start Dev Proxy - GitHub Actions step&lt;/figcaption&gt;
  1604. &lt;div class="highlight" title="Start Dev Proxy - GitHub Actions step"&gt;&lt;div class="chroma"&gt;
  1605. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1606. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  1607. &lt;/span&gt;&lt;span class="lnt"&gt;2
  1608. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1609. &lt;td class="lntd"&gt;
  1610. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1611. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy/devproxy &amp;amp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1612. &lt;/div&gt;
  1613. &lt;/div&gt;
  1614. &lt;/figure&gt;
  1615. &lt;h2 id="what-about-https-endpoints"&gt;What about HTTPS endpoints?&lt;/h2&gt;
  1616. &lt;p&gt;When you start using the Dev Proxy like this on GitHub Actions, you will encounter issues when calling HTTPS endpoints. In my case, I got the following error when testing it with Playwright:&lt;/p&gt;
  1617. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  1618. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1619. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  1620. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1621. &lt;td class="lntd"&gt;
  1622. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error: page.goto: net::ERR_PROXY_CONNECTION_FAILED at https://frontmatter.codes/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1623. &lt;/div&gt;
  1624. &lt;/div&gt;
  1625. &lt;p&gt;These issues are normal because to intercept HTTPS traffic, you must trust the root certificate which gets created by the Dev Proxy. When you run the Dev Proxy on your local machine for the first time, it will prompt you to trust the root certificate.&lt;/p&gt;
  1626. &lt;div class="caption my-4"&gt;
  1627. &lt;figure class="caption__figure"&gt;
  1628. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/devproxy-certificate.webp" title="Show image"&gt;
  1629. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  1630. &lt;img data-lqip="data:image/jpeg;base64,UklGRoIAAABXRUJQVlA4WAoAAAAQAAAACQAABQAAQUxQSC4AAAABL6AWAAI26NDVuSCCKSJiQnkoZGCF2mIIoJ1DAD&amp;#43;D9vxxUojof9Tu&amp;#43;knKNwADVlA4IC4AAADQAQCdASoKAAYAAUAmJZwAAu1/gr4MAAD&amp;#43;/r6vjpqRUZMmuwgpLIV87IcJAAAA" src="https://www.eliostruyf.com/uploads/2024/03/devproxy-certificate.webp" alt="Trust the self-signed certificate" style="width: 900px;" class="lazyload" /&gt;
  1631. &lt;/a&gt;
  1632. &lt;figcaption class="caption__text"&gt;Trust the self-signed certificate&lt;/figcaption&gt;
  1633. &lt;/figure&gt;
  1634. &lt;/div&gt;
  1635. &lt;p&gt;As it runs in a non-interactive mode in GitHub Actions, you cannot trust the certificate that way. To solve this, we will have to include a couple of extra steps in our workflow to trust the root certificate and be able to intercept the HTTPS traffic.&lt;/p&gt;
  1636. &lt;h2 id="trusting-the-root-certificate"&gt;Trusting the root certificate&lt;/h2&gt;
  1637. &lt;p&gt;When the Dev Proxy asks you to trust the root certificate, and you accept it, it uses the &lt;a href="https://github.com/microsoft/dev-proxy/blob/main/dev-proxy/trust-cert.sh"&gt;trust-cert.sh&lt;/a&gt; script to find the certificate and trust it.&lt;/p&gt;
  1638. &lt;p&gt;My first attempt was to run the &lt;code&gt;trust-cert.sh&lt;/code&gt; script in my GitHub Actions workflow. Unfortunately, this did not work because the &lt;code&gt;security add-trusted-cert&lt;/code&gt; command requires a password to add the certificate to the login keychain.&lt;/p&gt;
  1639. &lt;p&gt;After some research, I found an article about &lt;a href="https://twocanoes.com/trusting-certificates-in-system-keychain-without-prompting/"&gt;Trusting Certificates in System Keychain without Prompting&lt;/a&gt;. The article explains that you will not be prompted for a password when you add a certificate to the system keychain, and this will be our solution to make it work in our GitHub Actions workflow.&lt;/p&gt;
  1640. &lt;p&gt;The script to trust the certificate in the system keychain looks as follows:&lt;/p&gt;
  1641. &lt;figure class="codeblock_titled "&gt;
  1642. &lt;figcaption class="codeblock_titled__header"&gt;Trust certificate - GitHub Actions steps&lt;/figcaption&gt;
  1643. &lt;div class="highlight" title="Trust certificate - GitHub Actions steps"&gt;&lt;div class="chroma"&gt;
  1644. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1645. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  1646. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  1647. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  1648. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  1649. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  1650. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  1651. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  1652. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  1653. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  1654. &lt;/span&gt;&lt;span class="lnt"&gt;10
  1655. &lt;/span&gt;&lt;span class="lnt"&gt;11
  1656. &lt;/span&gt;&lt;span class="lnt"&gt;12
  1657. &lt;/span&gt;&lt;span class="lnt"&gt;13
  1658. &lt;/span&gt;&lt;span class="lnt"&gt;14
  1659. &lt;/span&gt;&lt;span class="lnt"&gt;15
  1660. &lt;/span&gt;&lt;span class="lnt"&gt;16
  1661. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1662. &lt;td class="lntd"&gt;
  1663. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install the Dev Proxy&amp;#39;s certificate&lt;/span&gt;&lt;span class="w"&gt;
  1664. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  1665. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  1666. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Finding certificate...&amp;#34;
  1667. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; security find-certificate -c &amp;#34;Dev Proxy CA&amp;#34; -a -p &amp;gt; dev-proxy-ca.pem
  1668. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1669. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Trusting certificate...&amp;#34;
  1670. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain dev-proxy-ca.pem
  1671. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Certificate trusted.&amp;#34;
  1672. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1673. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Set the system proxy settings (optional)
  1674. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;http_proxy=http://127.0.0.1:8000&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
  1675. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;https_proxy=http://127.0.0.1:8000&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
  1676. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1677. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Required to test CURL with the Dev Proxy (optional)
  1678. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;SSL_CERT_FILE=$GITHUB_WORKSPACE/dev-proxy-ca.pem&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1679. &lt;/div&gt;
  1680. &lt;/div&gt;
  1681. &lt;/figure&gt;
  1682. &lt;p&gt;This script is similar to the &lt;a href="https://github.com/microsoft/dev-proxy/blob/main/dev-proxy/trust-cert.sh"&gt;trust-cert.sh&lt;/a&gt; script, but instead of adding it to the login keychain, the certificate gets added to the system keychain.&lt;/p&gt;
  1683. &lt;p&gt;At the end of the script there are three exports which are all optional.&lt;/p&gt;
  1684. &lt;ul&gt;
  1685. &lt;li&gt;The &lt;code&gt;http_proxy&lt;/code&gt; and &lt;code&gt;https_proxy&lt;/code&gt; environment variables can be used to set the system proxy settings on the macOS virtual machine.&lt;/li&gt;
  1686. &lt;li&gt;The &lt;code&gt;SSL_CERT_FILE&lt;/code&gt; environment variable is required on macOS to make the &lt;code&gt;curl&lt;/code&gt; commands work with the Dev Proxy.&lt;/li&gt;
  1687. &lt;/ul&gt;
  1688. &lt;div class="caption my-4"&gt;
  1689. &lt;figure class="caption__figure"&gt;
  1690. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/devproxy-trust-certificate.webp" title="Show image"&gt;
  1691. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  1692. &lt;img data-lqip="data:image/jpeg;base64,UklGRlYAAABXRUJQVlA4WAoAAAAQAAAACQAAAQAAQUxQSA0AAAABD3Bx8oiIsAIR/Q8FAFZQOCAiAAAAsAEAnQEqCgACAAFAJiWcAnQBDvbEyAD&amp;#43;/uSR3Uvp2shmAA==" src="https://www.eliostruyf.com/uploads/2024/03/devproxy-trust-certificate.webp" alt="Trust the self-signed certificate on the macOS virtual machine" style="width: 900px;" class="lazyload" /&gt;
  1693. &lt;/a&gt;
  1694. &lt;figcaption class="caption__text"&gt;Trust the self-signed certificate on the macOS virtual machine&lt;/figcaption&gt;
  1695. &lt;/figure&gt;
  1696. &lt;/div&gt;
  1697. &lt;h2 id="the-complete-github-actions-workflow"&gt;The complete GitHub Actions workflow&lt;/h2&gt;
  1698. &lt;p&gt;Now that we have the certificate trust figured out, we can combine all the steps into a complete GitHub Actions workflow.&lt;/p&gt;
  1699. &lt;figure class="codeblock_titled "&gt;
  1700. &lt;figcaption class="codeblock_titled__header"&gt;Complete GitHub Actions workflow&lt;/figcaption&gt;
  1701. &lt;div class="highlight" title="Complete GitHub Actions workflow"&gt;&lt;div class="chroma"&gt;
  1702. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1703. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  1704. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  1705. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  1706. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  1707. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  1708. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  1709. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  1710. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  1711. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  1712. &lt;/span&gt;&lt;span class="lnt"&gt;10
  1713. &lt;/span&gt;&lt;span class="lnt"&gt;11
  1714. &lt;/span&gt;&lt;span class="lnt"&gt;12
  1715. &lt;/span&gt;&lt;span class="lnt"&gt;13
  1716. &lt;/span&gt;&lt;span class="lnt"&gt;14
  1717. &lt;/span&gt;&lt;span class="lnt"&gt;15
  1718. &lt;/span&gt;&lt;span class="lnt"&gt;16
  1719. &lt;/span&gt;&lt;span class="lnt"&gt;17
  1720. &lt;/span&gt;&lt;span class="lnt"&gt;18
  1721. &lt;/span&gt;&lt;span class="lnt"&gt;19
  1722. &lt;/span&gt;&lt;span class="lnt"&gt;20
  1723. &lt;/span&gt;&lt;span class="lnt"&gt;21
  1724. &lt;/span&gt;&lt;span class="lnt"&gt;22
  1725. &lt;/span&gt;&lt;span class="lnt"&gt;23
  1726. &lt;/span&gt;&lt;span class="lnt"&gt;24
  1727. &lt;/span&gt;&lt;span class="lnt"&gt;25
  1728. &lt;/span&gt;&lt;span class="lnt"&gt;26
  1729. &lt;/span&gt;&lt;span class="lnt"&gt;27
  1730. &lt;/span&gt;&lt;span class="lnt"&gt;28
  1731. &lt;/span&gt;&lt;span class="lnt"&gt;29
  1732. &lt;/span&gt;&lt;span class="lnt"&gt;30
  1733. &lt;/span&gt;&lt;span class="lnt"&gt;31
  1734. &lt;/span&gt;&lt;span class="lnt"&gt;32
  1735. &lt;/span&gt;&lt;span class="lnt"&gt;33
  1736. &lt;/span&gt;&lt;span class="lnt"&gt;34
  1737. &lt;/span&gt;&lt;span class="lnt"&gt;35
  1738. &lt;/span&gt;&lt;span class="lnt"&gt;36
  1739. &lt;/span&gt;&lt;span class="lnt"&gt;37
  1740. &lt;/span&gt;&lt;span class="lnt"&gt;38
  1741. &lt;/span&gt;&lt;span class="lnt"&gt;39
  1742. &lt;/span&gt;&lt;span class="lnt"&gt;40
  1743. &lt;/span&gt;&lt;span class="lnt"&gt;41
  1744. &lt;/span&gt;&lt;span class="lnt"&gt;42
  1745. &lt;/span&gt;&lt;span class="lnt"&gt;43
  1746. &lt;/span&gt;&lt;span class="lnt"&gt;44
  1747. &lt;/span&gt;&lt;span class="lnt"&gt;45
  1748. &lt;/span&gt;&lt;span class="lnt"&gt;46
  1749. &lt;/span&gt;&lt;span class="lnt"&gt;47
  1750. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1751. &lt;td class="lntd"&gt;
  1752. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;macOS Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1753. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1754. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1755. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1756. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1757. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
  1758. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
  1759. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1760. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1761. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1762. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1763. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
  1764. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;macos-latest&lt;/span&gt;&lt;span class="w"&gt;
  1765. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1766. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
  1767. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1768. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-node@v4&lt;/span&gt;&lt;span class="w"&gt;
  1769. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1770. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1771. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash -c &amp;#34;$(curl -sL https://aka.ms/devproxy/setup.sh)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
  1772. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1773. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1774. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./devproxy/devproxy &amp;amp;&lt;/span&gt;&lt;span class="w"&gt;
  1775. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1776. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install the Dev Proxy&amp;#39;s certificate&lt;/span&gt;&lt;span class="w"&gt;
  1777. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  1778. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  1779. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Finding certificate...&amp;#34;
  1780. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; security find-certificate -c &amp;#34;Dev Proxy CA&amp;#34; -a -p &amp;gt; dev-proxy-ca.pem
  1781. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1782. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Trusting certificate...&amp;#34;
  1783. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain dev-proxy-ca.pem
  1784. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Certificate trusted.&amp;#34;
  1785. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1786. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Set the system proxy settings (optional)
  1787. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;http_proxy=http://127.0.0.1:8000&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
  1788. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;https_proxy=http://127.0.0.1:8000&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
  1789. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
  1790. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Required to test CURL with the Dev Proxy (optional)
  1791. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;SSL_CERT_FILE=$GITHUB_WORKSPACE/dev-proxy-ca.pem&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;
  1792. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1793. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Include the additional steps you want to run after the Dev Proxy started&lt;/span&gt;&lt;span class="w"&gt;
  1794. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Test Dev Proxy&lt;/span&gt;&lt;span class="w"&gt;
  1795. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
  1796. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -ix http://127.0.0.1:8000 https://jsonplaceholder.typicode.com/posts
  1797. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # When you used the system proxy settings, you don&amp;#39;t need to specify the proxy in the curl command
  1798. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -i https://jsonplaceholder.typicode.com/posts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1799. &lt;/div&gt;
  1800. &lt;/div&gt;
  1801. &lt;/figure&gt;
  1802. &lt;div class="caption my-4"&gt;
  1803. &lt;figure class="caption__figure"&gt;
  1804. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/devproxy-with-trusted-root-certificate.webp" title="Show image"&gt;
  1805. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  1806. &lt;img data-lqip="data:image/jpeg;base64,UklGRnQAAABXRUJQVlA4WAoAAAAQAAAACQAABgAAQUxQSCQAAAABH6CQbQTIn3L3eNxpREScFWoCIGEY3vTPAD3NENH/WEPcvwZWUDggKgAAAPABAJ0BKgoABwABQCYlnALsAQN9qaGwgAD&amp;#43;/tQofIXyVWeusWS/egAAAA==" src="https://www.eliostruyf.com/uploads/2024/03/devproxy-with-trusted-root-certificate.webp" alt="Test Dev Proxy on GitHub Actions" style="width: 900px;" class="lazyload" /&gt;
  1807. &lt;/a&gt;
  1808. &lt;figcaption class="caption__text"&gt;Test Dev Proxy on GitHub Actions&lt;/figcaption&gt;
  1809. &lt;/figure&gt;
  1810. &lt;/div&gt;
  1811. &lt;p&gt;With this setup, you can use the Dev Proxy in your GitHub Actions workflow on a macOS virtual machine. This allows you to use the Dev Proxy in combination with Playwright or any other tool for testing your solutions.&lt;/p&gt;
  1812. &lt;blockquote class='info'&gt;
  1813. &lt;div class='mb-2'&gt;
  1814. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1815. &lt;/div&gt;
  1816. &lt;p&gt;
  1817. &lt;span&gt;Templates are available on the following &lt;a href="https://github.com/estruyf/devproxy-github-actions-templates"&gt;GitHub repository&lt;/a&gt;.&lt;/span&gt;
  1818. &lt;/p&gt;
  1819. &lt;/blockquote&gt;</content:encoded></item><item><title>#DevHack: Running a background service on GitHub Actions</title><link>https://www.eliostruyf.com/devhack-running-background-service-github-actions/</link><pubDate>Mon, 25 Mar 2024 09:05:07 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/devhack-running-background-service-github-actions/</guid><description>Running background services on GitHub Actions can be helpful when you want to run some tests. For instance, start up the local server before running the tests. In my case, I was testing out Dev Proxy on GitHub Actions to see if I could use it in combination with Playwright to provide my mocks for my tests.</description><content:encoded>&lt;p&gt;Running background services on GitHub Actions can be helpful when you want to run some tests. For instance, start up the local server before running the tests. In my case, I was testing out &lt;a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/overview"&gt;Dev Proxy&lt;/a&gt; on GitHub Actions to see if I could use it in combination with Playwright to provide my mocks for my tests. Unfortunately, GitHub Actions does not support running multiple steps in parallel, so I had to find a workaround.&lt;/p&gt;
  1820. &lt;p&gt;When searching for ways to run background processes on Linux environments, I found a solution to using an ampersand &lt;code&gt;&amp;amp;&lt;/code&gt; at the end of the command.&lt;/p&gt;
  1821. &lt;p&gt;Using the ampersand starts the service in the background and allows you to continue with the following command.&lt;/p&gt;
  1822. &lt;figure class="codeblock_titled "&gt;
  1823. &lt;figcaption class="codeblock_titled__header"&gt;Start a process in the background&lt;/figcaption&gt;
  1824. &lt;div class="highlight" title="Start a process in the background"&gt;&lt;div class="chroma"&gt;
  1825. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1826. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  1827. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1828. &lt;td class="lntd"&gt;
  1829. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm start &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1830. &lt;/div&gt;
  1831. &lt;/div&gt;
  1832. &lt;/figure&gt;
  1833. &lt;blockquote class='info'&gt;
  1834. &lt;div class='mb-2'&gt;
  1835. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1836. &lt;/div&gt;
  1837. &lt;p&gt;
  1838. &lt;span&gt;Thanks &lt;a href="https://michaelheap.com/"&gt;Michael Heap&lt;/a&gt; for confirming that this is the right approach.&lt;/span&gt;
  1839. &lt;/p&gt;
  1840. &lt;/blockquote&gt;
  1841. &lt;h2 id="starting-a-background-service-in-your-github-actions-workflow"&gt;Starting a background service in your GitHub Actions workflow&lt;/h2&gt;
  1842. &lt;p&gt;The same approach can be used in GitHub Actions. When you use the ampersand &lt;code&gt;&amp;amp;&lt;/code&gt; in your step, it starts up the service and continues to the next step in your workflow.&lt;/p&gt;
  1843. &lt;p&gt;Here is an example of how to start a service in the background and then run your tests.&lt;/p&gt;
  1844. &lt;figure class="codeblock_titled "&gt;
  1845. &lt;figcaption class="codeblock_titled__header"&gt;GitHub Actions Workflow&lt;/figcaption&gt;
  1846. &lt;div class="highlight" title="GitHub Actions Workflow"&gt;&lt;div class="chroma"&gt;
  1847. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1848. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  1849. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  1850. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  1851. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  1852. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  1853. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  1854. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  1855. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  1856. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  1857. &lt;/span&gt;&lt;span class="lnt"&gt;10
  1858. &lt;/span&gt;&lt;span class="lnt"&gt;11
  1859. &lt;/span&gt;&lt;span class="lnt"&gt;12
  1860. &lt;/span&gt;&lt;span class="lnt"&gt;13
  1861. &lt;/span&gt;&lt;span class="lnt"&gt;14
  1862. &lt;/span&gt;&lt;span class="lnt"&gt;15
  1863. &lt;/span&gt;&lt;span class="lnt"&gt;16
  1864. &lt;/span&gt;&lt;span class="lnt"&gt;17
  1865. &lt;/span&gt;&lt;span class="lnt"&gt;18
  1866. &lt;/span&gt;&lt;span class="lnt"&gt;19
  1867. &lt;/span&gt;&lt;span class="lnt"&gt;20
  1868. &lt;/span&gt;&lt;span class="lnt"&gt;21
  1869. &lt;/span&gt;&lt;span class="lnt"&gt;22
  1870. &lt;/span&gt;&lt;span class="lnt"&gt;23
  1871. &lt;/span&gt;&lt;span class="lnt"&gt;24
  1872. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1873. &lt;td class="lntd"&gt;
  1874. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run tests&lt;/span&gt;&lt;span class="w"&gt;
  1875. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1876. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1877. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1878. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1879. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
  1880. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
  1881. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1882. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1883. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1884. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
  1885. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  1886. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
  1887. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1888. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-node@v4&lt;/span&gt;&lt;span class="w"&gt;
  1889. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1890. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install dependencies&lt;/span&gt;&lt;span class="w"&gt;
  1891. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npm ci&lt;/span&gt;&lt;span class="w"&gt;
  1892. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1893. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Start service&lt;/span&gt;&lt;span class="w"&gt;
  1894. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npm start &amp;amp;&lt;/span&gt;&lt;span class="w"&gt;
  1895. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
  1896. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run tests&lt;/span&gt;&lt;span class="w"&gt;
  1897. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npm test&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1898. &lt;/div&gt;
  1899. &lt;/div&gt;
  1900. &lt;/figure&gt;
  1901. &lt;p&gt;In the above example, the &lt;code&gt;npm start &amp;amp;&lt;/code&gt; command starts the local server in the background and continues to the next step: running the tests.&lt;/p&gt;
  1902. &lt;p&gt;I hope this helps you when running a background service on GitHub Actions.&lt;/p&gt;</content:encoded></item><item><title>#DevHack: use the synchronous Azure translation API in Node</title><link>https://www.eliostruyf.com/devhack-synchronous-azure-translation-api-node/</link><pubDate>Thu, 21 Mar 2024 16:59:07 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/devhack-synchronous-azure-translation-api-node/</guid><description>The Azure AI Translator service has a new synchronous API in preview. The nice thing about this API is that it does not require any Azure Storage account to be set up to which you typically need to upload the files to be translated. Instead, you can just send the document to be translated directly to the API and you will get the translated document back.</description><content:encoded>&lt;p&gt;The Azure AI Translator service has a new synchronous API in preview. The nice thing about this API is that it does not require any Azure Storage account to be set up to which you typically need to upload the files to be translated. Instead, you can just send the document to be translated directly to the API and you will get the translated document back.&lt;/p&gt;
  1903. &lt;blockquote class='info'&gt;
  1904. &lt;div class='mb-2'&gt;
  1905. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1906. &lt;/div&gt;
  1907. &lt;p&gt;
  1908. &lt;span&gt;You can read more information about the synchronous API on the &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/translator/document-translation/quickstarts/synchronous-rest-api"&gt;Get started with synchronous translation&lt;/a&gt; article.&lt;/span&gt;
  1909. &lt;/p&gt;
  1910. &lt;/blockquote&gt;
  1911. &lt;p&gt;In this post, I will show you how to use the synchronous Azure translation API in Node.js.&lt;/p&gt;
  1912. &lt;h2 id="calling-the-synchronous-azure-translation-api"&gt;Calling the synchronous Azure translation API&lt;/h2&gt;
  1913. &lt;p&gt;To call the synchronous Azure translation API, you need to send a POST request to the &lt;code&gt;https://{your-instance}.cognitiveservices.azure.com/translator/document:translate&lt;/code&gt; endpoint with form data containing the document to be translated.&lt;/p&gt;
  1914. &lt;p&gt;Here is an example of how you can call the synchronous Azure translation API in Node.js:&lt;/p&gt;
  1915. &lt;figure class="codeblock_titled "&gt;
  1916. &lt;figcaption class="codeblock_titled__header"&gt;Example of calling the synchronous Azure translation API&lt;/figcaption&gt;
  1917. &lt;div class="highlight" title="Example of calling the synchronous Azure translation API"&gt;&lt;div class="chroma"&gt;
  1918. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  1919. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  1920. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  1921. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  1922. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  1923. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  1924. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  1925. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  1926. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  1927. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  1928. &lt;/span&gt;&lt;span class="lnt"&gt;10
  1929. &lt;/span&gt;&lt;span class="lnt"&gt;11
  1930. &lt;/span&gt;&lt;span class="lnt"&gt;12
  1931. &lt;/span&gt;&lt;span class="lnt"&gt;13
  1932. &lt;/span&gt;&lt;span class="lnt"&gt;14
  1933. &lt;/span&gt;&lt;span class="lnt"&gt;15
  1934. &lt;/span&gt;&lt;span class="lnt"&gt;16
  1935. &lt;/span&gt;&lt;span class="lnt"&gt;17
  1936. &lt;/span&gt;&lt;span class="lnt"&gt;18
  1937. &lt;/span&gt;&lt;span class="lnt"&gt;19
  1938. &lt;/span&gt;&lt;span class="lnt"&gt;20
  1939. &lt;/span&gt;&lt;span class="lnt"&gt;21
  1940. &lt;/span&gt;&lt;span class="lnt"&gt;22
  1941. &lt;/span&gt;&lt;span class="lnt"&gt;23
  1942. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  1943. &lt;td class="lntd"&gt;
  1944. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;node:fs/promises&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1945. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;node:stream/consumers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1946. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1947. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceLanguage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;en&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1948. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetLanguage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1949. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1950. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://{your-instance}.cognitiveservices.azure.com/translator/document:translate?sourceLanguage={sourceLanguage}&amp;amp;targetLanguage={targetLanguage}&amp;amp;api-version=2023-11-01-preview&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  1951. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1952. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1953. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1954. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1955. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Ocp-Apim-Subscription-Key&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{your-subscription-key}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  1956. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  1957. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;
  1958. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  1959. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  1960. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1961. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Error: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  1962. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  1963. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  1964. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  1965. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  1966. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  1967. &lt;/div&gt;
  1968. &lt;/div&gt;
  1969. &lt;/figure&gt;
  1970. &lt;p&gt;That&amp;rsquo;s it! You have now successfully called the synchronous Azure translation API in Node.js.&lt;/p&gt;</content:encoded></item><item><title>Locally verifying GitHub Actions Job Summaries</title><link>https://www.eliostruyf.com/locally-verifying-github-actions-job-summaries/</link><pubDate>Mon, 11 Mar 2024 09:46:31 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/locally-verifying-github-actions-job-summaries/</guid><description>GitHub Actions Job Summaries are a great way to provide more information on your job&amp;rsquo;s output. This summary is shown in the Actions tab of your repository.
  1971. Show image GitHub Actions reporter for Playwright with details markup info You can read more about it on Supercharging GitHub Actions with Job Summaries In this post, I&amp;rsquo;ll explain how you can locally develop and test your GitHub Actions Job Summary outputs using the @actions/core dependency.</description><content:encoded>&lt;p&gt;GitHub Actions Job Summaries are a great way to provide more information on your job&amp;rsquo;s output. This summary is shown in the Actions tab of your repository.&lt;/p&gt;
  1972. &lt;div class="caption my-4"&gt;
  1973. &lt;figure class="caption__figure"&gt;
  1974. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2023/08/example-with-details.png" title="Show image"&gt;
  1975. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  1976. &lt;img data-lqip="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAADCAYAAACqPZ51AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAbklEQVR4nGMwNrObrWtsdVXX2Pq0uo7JWUU1nbNK6npgrKyhd1pJXe&amp;#43;akrruNAZ9C6uDRpaO/7X0zf&amp;#43;qaBn&amp;#43;V9U2QsKGf9R1Tf6raBvtZtA0NpunbWJ5U8fQ4ryyhv5FJXU9ZHxeUU3vlqKa3kwArS4odypGMBsAAAAASUVORK5CYII=" src="https://www.eliostruyf.com/uploads/2023/08/example-with-details.png" alt="GitHub Actions reporter for Playwright with details markup" style="width: 1287px;" class="lazyload" /&gt;
  1977. &lt;/a&gt;
  1978. &lt;figcaption class="caption__text"&gt;GitHub Actions reporter for Playwright with details markup&lt;/figcaption&gt;
  1979. &lt;/figure&gt;
  1980. &lt;/div&gt;
  1981. &lt;blockquote class='info'&gt;
  1982. &lt;div class='mb-2'&gt;
  1983. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1984. &lt;/div&gt;
  1985. &lt;p&gt;
  1986. &lt;span&gt;You can read more about it on &lt;a href="https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/"&gt;Supercharging GitHub Actions with Job Summaries&lt;/a&gt;&lt;/span&gt;
  1987. &lt;/p&gt;
  1988. &lt;/blockquote&gt;
  1989. &lt;p&gt;In this post, I&amp;rsquo;ll explain how you can locally develop and test your GitHub Actions Job Summary outputs using the &lt;a href="https://www.npmjs.com/package/@actions/core"&gt;@actions/core&lt;/a&gt; dependency.&lt;/p&gt;
  1990. &lt;blockquote class='info'&gt;
  1991. &lt;div class='mb-2'&gt;
  1992. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  1993. &lt;/div&gt;
  1994. &lt;p&gt;
  1995. &lt;span&gt;Testing your summary locally will save you some time, as you don&amp;rsquo;t have to push your changes to GitHub to see if your summary output is working as expected.&lt;/span&gt;
  1996. &lt;/p&gt;
  1997. &lt;/blockquote&gt;
  1998. &lt;p&gt;In my case, I used this approach to develop the &lt;a href="https://www.npmjs.com/package/@estruyf/github-actions-reporter"&gt;GitHub Actions Reporter for Playwright&lt;/a&gt;. This npm package generates a summary output for your Playwright tests.&lt;/p&gt;
  1999. &lt;h2 id="how-to-write-a-summary-output"&gt;How to write a summary output&lt;/h2&gt;
  2000. &lt;p&gt;The easiest way to write a GitHub Actions summary is to use the &lt;a href="https://www.npmjs.com/package/@actions/core"&gt;@actions/core&lt;/a&gt; dependency. The &lt;code&gt;@actions/core&lt;/code&gt; dependency provides functions for inputs, outputs, logging, and more.&lt;/p&gt;
  2001. &lt;p&gt;You can write the job summary using the &lt;code&gt;core.summary&lt;/code&gt; methods. Once completed, you can call the &lt;code&gt;core.summary.write()&lt;/code&gt; method to write the buffer to the Job Summary output on GitHub Actions.&lt;/p&gt;
  2002. &lt;figure class="codeblock_titled "&gt;
  2003. &lt;figcaption class="codeblock_titled__header"&gt;generateSummary.mjs | Summary sample&lt;/figcaption&gt;
  2004. &lt;div class="highlight" title="generateSummary.mjs | Summary sample"&gt;&lt;div class="chroma"&gt;
  2005. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2006. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  2007. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  2008. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  2009. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  2010. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  2011. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  2012. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  2013. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  2014. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  2015. &lt;/span&gt;&lt;span class="lnt"&gt;10
  2016. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2017. &lt;td class="lntd"&gt;
  2018. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;core&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@actions/core&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2019. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2020. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2021. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2022. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addHeading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;My job summary heading&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2023. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addSeparator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  2024. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2025. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addList&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2026. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2027. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2028. &lt;/div&gt;
  2029. &lt;/div&gt;
  2030. &lt;/figure&gt;
  2031. &lt;p&gt;The downside of this approach is that you have to push your changes to GitHub to see if your summary output is working as expected. When you try to run it locally, it will throw an error saying it is unable to find the &lt;code&gt;GITHUB_STEP_SUMMARY&lt;/code&gt; environment variable.&lt;/p&gt;
  2032. &lt;h2 id="testing-your-summary-locally"&gt;Testing your summary locally&lt;/h2&gt;
  2033. &lt;p&gt;To test your summary locally, you must set the &lt;code&gt;GITHUB_STEP_SUMMARY&lt;/code&gt; environment variable. This variable defines the file path where the summary output should be written. Locally, we can pass a local file path to this environment variable.&lt;/p&gt;
  2034. &lt;p&gt;Here is an example of how to set the &lt;code&gt;GITHUB_STEP_SUMMARY&lt;/code&gt; environment variable when running your script locally.&lt;/p&gt;
  2035. &lt;figure class="codeblock_titled "&gt;
  2036. &lt;figcaption class="codeblock_titled__header"&gt;generateSummary.mjs | Local job summary testing&lt;/figcaption&gt;
  2037. &lt;div class="highlight" title="generateSummary.mjs | Local job summary testing"&gt;&lt;div class="chroma"&gt;
  2038. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2039. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  2040. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  2041. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  2042. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  2043. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  2044. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  2045. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  2046. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  2047. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  2048. &lt;/span&gt;&lt;span class="lnt"&gt;10
  2049. &lt;/span&gt;&lt;span class="lnt"&gt;11
  2050. &lt;/span&gt;&lt;span class="lnt"&gt;12
  2051. &lt;/span&gt;&lt;span class="lnt"&gt;13
  2052. &lt;/span&gt;&lt;span class="lnt"&gt;14
  2053. &lt;/span&gt;&lt;span class="lnt"&gt;15
  2054. &lt;/span&gt;&lt;span class="lnt"&gt;16
  2055. &lt;/span&gt;&lt;span class="lnt"&gt;17
  2056. &lt;/span&gt;&lt;span class="lnt"&gt;18
  2057. &lt;/span&gt;&lt;span class="lnt"&gt;19
  2058. &lt;/span&gt;&lt;span class="lnt"&gt;20
  2059. &lt;/span&gt;&lt;span class="lnt"&gt;21
  2060. &lt;/span&gt;&lt;span class="lnt"&gt;22
  2061. &lt;/span&gt;&lt;span class="lnt"&gt;23
  2062. &lt;/span&gt;&lt;span class="lnt"&gt;24
  2063. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2064. &lt;td class="lntd"&gt;
  2065. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;core&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@actions/core&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2066. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2067. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unlinkSync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writeFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2068. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2069. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2070. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2071. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;development&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2072. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summaryFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;summary.html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2073. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summaryFile&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2074. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;unlinkSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summaryFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2075. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  2076. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summaryFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2077. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_STEP_SUMMARY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;summaryFile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2078. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_ACTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2079. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  2080. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2081. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2082. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2083. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addHeading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;My job summary heading&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2084. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addSeparator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  2085. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2086. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addList&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2087. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2088. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2089. &lt;/div&gt;
  2090. &lt;/div&gt;
  2091. &lt;/figure&gt;
  2092. &lt;p&gt;When you run your script locally, you will see that the &lt;code&gt;summary.html&lt;/code&gt; file is created with the content of your summary output. The generated file contents look like this:&lt;/p&gt;
  2093. &lt;figure class="codeblock_titled "&gt;
  2094. &lt;figcaption class="codeblock_titled__header"&gt;summary.html&lt;/figcaption&gt;
  2095. &lt;div class="highlight" title="summary.html"&gt;&lt;div class="chroma"&gt;
  2096. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2097. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2098. &lt;/span&gt;&lt;span class="lnt"&gt;2
  2099. &lt;/span&gt;&lt;span class="lnt"&gt;3
  2100. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2101. &lt;td class="lntd"&gt;
  2102. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;My job summary heading&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  2103. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;hr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  2104. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;item1&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;item2&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;item3&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2105. &lt;/div&gt;
  2106. &lt;/div&gt;
  2107. &lt;/figure&gt;</content:encoded></item><item><title>Using CLI for Microsoft 365 in TypeScript Azure Functions</title><link>https://www.eliostruyf.com/cli-microsoft-365-typescript-azure-functions/</link><pubDate>Thu, 07 Mar 2024 09:11:17 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/cli-microsoft-365-typescript-azure-functions/</guid><description>The CLI for Microsoft 365 allows you to manage your Microsoft 365 tenant settings and data. It provides a powerful and flexible way to automate tasks for Microsoft 365, and lately, I have been using it in my Azure Functions to automate a couple of tasks.
  2108. In this article, I will show you how to use the CLI for Microsoft 365 in TypeScript Azure Functions by explaining the following:</description><content:encoded>&lt;p&gt;The CLI for Microsoft 365 allows you to manage your Microsoft 365 tenant settings and data. It provides a powerful and flexible way to automate tasks for Microsoft 365, and lately, I have been using it in my Azure Functions to automate a couple of tasks.&lt;/p&gt;
  2109. &lt;p&gt;In this article, I will show you how to use the CLI for Microsoft 365 in TypeScript Azure Functions by explaining the following:&lt;/p&gt;
  2110. &lt;ul&gt;
  2111. &lt;li&gt;Configuring certificate-based authentication&lt;/li&gt;
  2112. &lt;li&gt;Using the certificate logging in the Azure Function for CLI for Microsoft 365&lt;/li&gt;
  2113. &lt;li&gt;Using CLI for Microsoft 365 in TypeScript Azure Functions&lt;/li&gt;
  2114. &lt;/ul&gt;
  2115. &lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
  2116. &lt;p&gt;To follow along with this article, you will need the following:&lt;/p&gt;
  2117. &lt;h3 id="local-development-environment"&gt;Local development environment&lt;/h3&gt;
  2118. &lt;ul&gt;
  2119. &lt;li&gt;Node.js&lt;/li&gt;
  2120. &lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?pivots=programming-language-typescript"&gt;Azure Functions Core Tools&lt;/a&gt;&lt;/li&gt;
  2121. &lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli"&gt;Azure CLI&lt;/a&gt;: The Azure CLI is used to authenticate with the Azure Key Vault locally&lt;/li&gt;
  2122. &lt;/ul&gt;
  2123. &lt;h3 id="microsoft-365-tenant-and-azure-subscription"&gt;Microsoft 365 tenant and Azure subscription&lt;/h3&gt;
  2124. &lt;ul&gt;
  2125. &lt;li&gt;Azure subscription
  2126. &lt;ul&gt;
  2127. &lt;li&gt;Azure Key Vault&lt;/li&gt;
  2128. &lt;/ul&gt;
  2129. &lt;/li&gt;
  2130. &lt;li&gt;Microsoft 365 tenant&lt;/li&gt;
  2131. &lt;/ul&gt;
  2132. &lt;h3 id="useful-visual-studio-code-extensions"&gt;Useful Visual Studio Code extensions&lt;/h3&gt;
  2133. &lt;ul&gt;
  2134. &lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureresourcegroups"&gt;Azure Resource&lt;/a&gt;&lt;/li&gt;
  2135. &lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions"&gt;Azure Functions&lt;/a&gt;&lt;/li&gt;
  2136. &lt;/ul&gt;
  2137. &lt;h2 id="configuring-certificate-based-authentication"&gt;Configuring certificate-based authentication&lt;/h2&gt;
  2138. &lt;p&gt;I chose to use certificate-based authentication for the authentication, as it is a secure way to authenticate with Microsoft 365 services and because my Azure subscription is not linked to my Microsoft 365 tenant. Certificate-based authentication can also be used for multi-tenant applications.&lt;/p&gt;
  2139. &lt;p&gt;First, you must create a certificate instead of generating this locally, as I used the Azure Key Vault. We can use the Azure Key Vault to generate a self-signed certificate, so we do not have to create it locally and upload the certificate.&lt;/p&gt;
  2140. &lt;h3 id="create-a-self-signed-certificate-in-azure-key-vault"&gt;Create a self-signed certificate in Azure Key Vault&lt;/h3&gt;
  2141. &lt;p&gt;On your Azure portal, navigate to your Azure Key Vault (or create one if you do not have one). Then, follow the steps below to make a self-signed certificate:&lt;/p&gt;
  2142. &lt;ul&gt;
  2143. &lt;li&gt;Click on the &lt;code&gt;Certificates&lt;/code&gt; in the left menu&lt;/li&gt;
  2144. &lt;li&gt;Click on &lt;code&gt;Generate/Import&lt;/code&gt; in the top menu&lt;/li&gt;
  2145. &lt;li&gt;Fill in the form with the following details:
  2146. &lt;ul&gt;
  2147. &lt;li&gt;Method: &lt;code&gt;Generate&lt;/code&gt;&lt;/li&gt;
  2148. &lt;li&gt;Certificate Name: &lt;code&gt;cli-m365-cert&lt;/code&gt; (or any name you prefer)&lt;/li&gt;
  2149. &lt;li&gt;Type: &lt;code&gt;Self-signed certificate&lt;/code&gt;&lt;/li&gt;
  2150. &lt;li&gt;Subject: &lt;code&gt;CN=cli-m365-cert&lt;/code&gt; (or any name you prefer)&lt;/li&gt;
  2151. &lt;li&gt;Validity: &lt;code&gt;12 months&lt;/code&gt; (or any duration you prefer)&lt;/li&gt;
  2152. &lt;li&gt;Key type: &lt;code&gt;PKCS #12&lt;/code&gt;&lt;/li&gt;
  2153. &lt;/ul&gt;
  2154. &lt;/li&gt;
  2155. &lt;/ul&gt;
  2156. &lt;div class="caption my-4"&gt;
  2157. &lt;figure class="caption__figure"&gt;
  2158. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/create-certificate.webp" title="Show image"&gt;
  2159. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2160. &lt;img data-lqip="data:image/jpeg;base64,UklGRnIAAABXRUJQVlA4WAoAAAAQAAAACQAABgAAQUxQSCIAAAABH0AgQPF/ZzGDGxERZ4WaAEgYhjf9M0BPM0T0P9YQ968BVlA4ICoAAACQAQCdASoKAAcAAUAmJaQAAudZfggA/v6FqOAmYW&amp;#43;OZDncWPHTK2SZAAA=" src="https://www.eliostruyf.com/uploads/2024/03/create-certificate.webp" alt="Create a self-signed certificate" style="width: 1772px;" class="lazyload" /&gt;
  2161. &lt;/a&gt;
  2162. &lt;figcaption class="caption__text"&gt;Create a self-signed certificate&lt;/figcaption&gt;
  2163. &lt;/figure&gt;
  2164. &lt;/div&gt;
  2165. &lt;ul&gt;
  2166. &lt;li&gt;Click on &lt;code&gt;Create&lt;/code&gt;&lt;/li&gt;
  2167. &lt;/ul&gt;
  2168. &lt;p&gt;Please give it a couple of seconds to generate the certificate. Once the certificate is generated, click on the certificate name and follow the steps to download the certificate:&lt;/p&gt;
  2169. &lt;ul&gt;
  2170. &lt;li&gt;Click on the certificate under the &lt;code&gt;current version&lt;/code&gt; column&lt;/li&gt;
  2171. &lt;li&gt;Click on &lt;code&gt;Download in CER format&lt;/code&gt; in the top menu&lt;/li&gt;
  2172. &lt;/ul&gt;
  2173. &lt;div class="caption my-4"&gt;
  2174. &lt;figure class="caption__figure"&gt;
  2175. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/download-cer.webp" title="Show image"&gt;
  2176. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2177. &lt;img data-lqip="data:image/jpeg;base64,UklGRlwAAABXRUJQVlA4WAoAAAAQAAAACQAAAQAAQUxQSA0AAAABD3Bu74iIsAIR/Q8FAFZQOCAoAAAAsAEAnQEqCgACAAFAJiWkAuwA3RhbgAD&amp;#43;/Ddtnerr4n56hRi7i&amp;#43;3gAA==" src="https://www.eliostruyf.com/uploads/2024/03/download-cer.webp" alt="Download in CER format" style="width: 1300px;" class="lazyload" /&gt;
  2178. &lt;/a&gt;
  2179. &lt;figcaption class="caption__text"&gt;Download in CER format&lt;/figcaption&gt;
  2180. &lt;/figure&gt;
  2181. &lt;/div&gt;
  2182. &lt;ul&gt;
  2183. &lt;li&gt;Keep the downloaded file safe, as we will use it in one of the upcoming steps&lt;/li&gt;
  2184. &lt;/ul&gt;
  2185. &lt;h3 id="create-a-microsoft-entra-id---app-registration"&gt;Create a Microsoft Entra ID - App registration&lt;/h3&gt;
  2186. &lt;p&gt;Next, we need to create an app registration for the Azure portal. The App registration will be used to authenticate with the CLI for Microsoft 365 to your tenant.&lt;/p&gt;
  2187. &lt;blockquote class='important'&gt;
  2188. &lt;div class='mb-2'&gt;
  2189. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  2190. &lt;/div&gt;
  2191. &lt;p&gt;
  2192. &lt;span&gt;Make sure you create the new app registration on the tenant linked to your Micorosft 365 environment&lt;/span&gt;
  2193. &lt;/p&gt;
  2194. &lt;/blockquote&gt;
  2195. &lt;p&gt;On the CLI for Microsoft 365 documentation, you can follow the &lt;a href="https://pnp.github.io/cli-microsoft365/user-guide/using-own-identity"&gt;Use your own Microsoft 365 Entra ID identity&lt;/a&gt; steps to create a new App registration.&lt;/p&gt;
  2196. &lt;blockquote class='important'&gt;
  2197. &lt;div class='mb-2'&gt;
  2198. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  2199. &lt;/div&gt;
  2200. &lt;p&gt;
  2201. &lt;span&gt;Follow it until you have to assign the permissions to the App registration.&lt;/span&gt;
  2202. &lt;/p&gt;
  2203. &lt;/blockquote&gt;
  2204. &lt;p&gt;When you followed the steps on the documentation, you should have done the following:&lt;/p&gt;
  2205. &lt;ul&gt;
  2206. &lt;li&gt;Created a new App registration&lt;/li&gt;
  2207. &lt;li&gt;Configured the authentication for console applications&lt;/li&gt;
  2208. &lt;li&gt;Enabled the public client flows&lt;/li&gt;
  2209. &lt;/ul&gt;
  2210. &lt;p&gt;You did have to stop at the permissions because we will assign the permissions in the next step ourselves. The permissions in the documentation are for user-based (delegate) permissions, but we will use application-based permissions.&lt;/p&gt;
  2211. &lt;h4 id="assign-the-permissions-to-the-app-registration"&gt;Assign the permissions to the App registration&lt;/h4&gt;
  2212. &lt;p&gt;For this example, I will use the minimum permissions required to read the sites in the Microsoft 365 tenant:&lt;/p&gt;
  2213. &lt;ul&gt;
  2214. &lt;li&gt;Microsoft Graph: &lt;code&gt;Sites.Read.All&lt;/code&gt;&lt;/li&gt;
  2215. &lt;li&gt;SharePoint: &lt;code&gt;Sites.Read.All&lt;/code&gt;&lt;/li&gt;
  2216. &lt;/ul&gt;
  2217. &lt;blockquote class='important'&gt;
  2218. &lt;div class='mb-2'&gt;
  2219. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  2220. &lt;/div&gt;
  2221. &lt;p&gt;
  2222. &lt;span&gt;Make sure you have the required permissions to assign the permissions to the App registration&lt;/span&gt;
  2223. &lt;/p&gt;
  2224. &lt;/blockquote&gt;
  2225. &lt;p&gt;To assign the permissions to the App registration, follow the steps below:&lt;/p&gt;
  2226. &lt;ul&gt;
  2227. &lt;li&gt;Navigate to the App registration you created&lt;/li&gt;
  2228. &lt;li&gt;Click on &lt;code&gt;API permissions&lt;/code&gt; in the left menu&lt;/li&gt;
  2229. &lt;li&gt;Click on &lt;code&gt;Add a permission&lt;/code&gt; in the top menu&lt;/li&gt;
  2230. &lt;li&gt;Click on &lt;code&gt;Microsoft Graph&lt;/code&gt; in the list&lt;/li&gt;
  2231. &lt;li&gt;Click on &lt;code&gt;Application permissions&lt;/code&gt;&lt;/li&gt;
  2232. &lt;li&gt;Search for &lt;code&gt;Sites&lt;/code&gt; and select the &lt;code&gt;Sites.Read.All&lt;/code&gt; permission&lt;/li&gt;
  2233. &lt;li&gt;Click on &lt;code&gt;Add permissions&lt;/code&gt;&lt;/li&gt;
  2234. &lt;li&gt;Click on &lt;code&gt;Add a permission&lt;/code&gt; in the top menu&lt;/li&gt;
  2235. &lt;li&gt;Click on &lt;code&gt;SharePoint&lt;/code&gt; in the list&lt;/li&gt;
  2236. &lt;li&gt;Click on &lt;code&gt;Application permissions&lt;/code&gt;&lt;/li&gt;
  2237. &lt;li&gt;Search for &lt;code&gt;Sites&lt;/code&gt; and select the &lt;code&gt;Sites.Read.All&lt;/code&gt; permission&lt;/li&gt;
  2238. &lt;li&gt;Click on &lt;code&gt;Add permissions&lt;/code&gt;&lt;/li&gt;
  2239. &lt;/ul&gt;
  2240. &lt;p&gt;Once you have added the permissions, you need to grant admin consent to the permissions. To do this, click on &lt;code&gt;Grant admin consent for &amp;lt;your tenant&amp;gt;&lt;/code&gt; in the top menu.&lt;/p&gt;
  2241. &lt;div class="caption my-4"&gt;
  2242. &lt;figure class="caption__figure"&gt;
  2243. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/application-permissions.webp" title="Show image"&gt;
  2244. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2245. &lt;img data-lqip="data:image/jpeg;base64,UklGRnYAAABXRUJQVlA4WAoAAAAQAAAACQAAAwAAQUxQSCAAAAABHyAWTPzd&amp;#43;sOZRkSEDMUNyMBR8aLHeCKi/9E8IULNA1ZQOCAwAAAA0AEAnQEqCgAEAAFAJiWcAAL3hXg8nVgA/vvXhv8Da2KCvPq1xgXArACULNh5AAAA" src="https://www.eliostruyf.com/uploads/2024/03/application-permissions.webp" alt="Configured the application permissions" style="width: 1924px;" class="lazyload" /&gt;
  2246. &lt;/a&gt;
  2247. &lt;figcaption class="caption__text"&gt;Configured the application permissions&lt;/figcaption&gt;
  2248. &lt;/figure&gt;
  2249. &lt;/div&gt;
  2250. &lt;h4 id="configure-the-certificate-in-the-app-registration"&gt;Configure the certificate in the App registration&lt;/h4&gt;
  2251. &lt;p&gt;Now that we have the app registration and the permissions configured, we need to configure the certificate for the app registration. By adding the previously created certificate to the App registration, we can use it to authenticate with the CLI for Microsoft 365. Follow the next steps to configure the certificate:&lt;/p&gt;
  2252. &lt;ul&gt;
  2253. &lt;li&gt;Navigate to the App registration you created&lt;/li&gt;
  2254. &lt;li&gt;Click on &lt;code&gt;Certificates &amp;amp; secrets&lt;/code&gt; in the left menu&lt;/li&gt;
  2255. &lt;li&gt;Click on &lt;code&gt;Upload certificate&lt;/code&gt; in the top menu&lt;/li&gt;
  2256. &lt;li&gt;Click on &lt;code&gt;Choose file&lt;/code&gt; and select the downloaded CER file&lt;/li&gt;
  2257. &lt;li&gt;Click on &lt;code&gt;Add&lt;/code&gt;&lt;/li&gt;
  2258. &lt;/ul&gt;
  2259. &lt;p&gt;Once you have uploaded the certificate, you will see the certificate in the list.&lt;/p&gt;
  2260. &lt;div class="caption my-4"&gt;
  2261. &lt;figure class="caption__figure"&gt;
  2262. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/upload-certificate.webp" title="Show image"&gt;
  2263. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2264. &lt;img data-lqip="data:image/jpeg;base64,UklGRlYAAABXRUJQVlA4WAoAAAAQAAAACQAAAQAAQUxQSAwAAAABD1D1iAisQET/QwFWUDggJAAAALABAJ0BKgoAAgABQCYlpAAC6NzWBcAA/v3UyCwg&amp;#43;Lq24AAAAA==" src="https://www.eliostruyf.com/uploads/2024/03/upload-certificate.webp" alt="Uploaded certificate to your app registration" style="width: 2122px;" class="lazyload" /&gt;
  2265. &lt;/a&gt;
  2266. &lt;figcaption class="caption__text"&gt;Uploaded certificate to your app registration&lt;/figcaption&gt;
  2267. &lt;/figure&gt;
  2268. &lt;/div&gt;
  2269. &lt;h2 id="creating-the-azure-functions-project"&gt;Creating the Azure Functions project&lt;/h2&gt;
  2270. &lt;p&gt;The previous configuration was required to start building our Azure Functions project. We will use the Azure Functions Core Tools to create the Azure Functions project.&lt;/p&gt;
  2271. &lt;blockquote class='info'&gt;
  2272. &lt;div class='mb-2'&gt;
  2273. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  2274. &lt;/div&gt;
  2275. &lt;p&gt;
  2276. &lt;span&gt;If you do not have the Azure Functions Core Tools installed, you can install it by following the &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?pivots=programming-language-typescript"&gt;official documentation&lt;/a&gt;.&lt;/span&gt;
  2277. &lt;/p&gt;
  2278. &lt;/blockquote&gt;
  2279. &lt;h3 id="initialize-the-azure-functions-project"&gt;Initialize the Azure Functions project&lt;/h3&gt;
  2280. &lt;p&gt;I created a new Azure Functions project using the following command:&lt;/p&gt;
  2281. &lt;figure class="codeblock_titled "&gt;
  2282. &lt;figcaption class="codeblock_titled__header"&gt;Initialize the Azure Functions project&lt;/figcaption&gt;
  2283. &lt;div class="highlight" title="Initialize the Azure Functions project"&gt;&lt;div class="chroma"&gt;
  2284. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2285. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2286. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2287. &lt;td class="lntd"&gt;
  2288. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;func init cli-m365-azurefunctions-sample --typescript&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2289. &lt;/div&gt;
  2290. &lt;/div&gt;
  2291. &lt;/figure&gt;
  2292. &lt;h3 id="install-the-required-packages"&gt;Install the required packages&lt;/h3&gt;
  2293. &lt;p&gt;Next, navigate to the created project and install the required packages:&lt;/p&gt;
  2294. &lt;figure class="codeblock_titled "&gt;
  2295. &lt;figcaption class="codeblock_titled__header"&gt;Install dependencies&lt;/figcaption&gt;
  2296. &lt;div class="highlight" title="Install dependencies"&gt;&lt;div class="chroma"&gt;
  2297. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2298. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2299. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2300. &lt;td class="lntd"&gt;
  2301. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm i @azure/identity @azure/keyvault-secrets @pnp/cli-microsoft365&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2302. &lt;/div&gt;
  2303. &lt;/div&gt;
  2304. &lt;/figure&gt;
  2305. &lt;p&gt;We installed the &lt;code&gt;@pnp/cli-microsoft365&lt;/code&gt; package to the Azure Functions project instead of globally installing it.&lt;/p&gt;
  2306. &lt;blockquote class='info'&gt;
  2307. &lt;div class='mb-2'&gt;
  2308. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  2309. &lt;/div&gt;
  2310. &lt;p&gt;
  2311. &lt;span&gt;The &lt;code&gt;@azure/identity&lt;/code&gt; and &lt;code&gt;@azure/keyvault-secrets&lt;/code&gt; packages are used to authenticate with the Azure Key Vault.&lt;/span&gt;
  2312. &lt;/p&gt;
  2313. &lt;/blockquote&gt;
  2314. &lt;h3 id="configure-the-required-settings"&gt;Configure the required settings&lt;/h3&gt;
  2315. &lt;p&gt;Open the project in Visual Studio Code and navigate to the &lt;code&gt;local.settings.json&lt;/code&gt; file. Add the following settings to the file:&lt;/p&gt;
  2316. &lt;ul&gt;
  2317. &lt;li&gt;&lt;code&gt;KeyVaultUrl&lt;/code&gt;: The URL of the Azure Key Vault&lt;/li&gt;
  2318. &lt;li&gt;&lt;code&gt;CertificateName&lt;/code&gt;: The name of the certificate in the Azure Key Vault&lt;/li&gt;
  2319. &lt;li&gt;&lt;code&gt;TenantId&lt;/code&gt;: The ID of your Microsoft 365 tenant. You can find the Tenant ID on the Microsoft Entra ID overview page.&lt;/li&gt;
  2320. &lt;li&gt;&lt;code&gt;ClientId&lt;/code&gt;: The Application (client) ID of the App registration you created&lt;/li&gt;
  2321. &lt;/ul&gt;
  2322. &lt;p&gt;The settings should look similar to this:&lt;/p&gt;
  2323. &lt;figure class="codeblock_titled "&gt;
  2324. &lt;figcaption class="codeblock_titled__header"&gt;local.settings.json&lt;/figcaption&gt;
  2325. &lt;div class="highlight" title="local.settings.json"&gt;&lt;div class="chroma"&gt;
  2326. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2327. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  2328. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  2329. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  2330. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  2331. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  2332. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  2333. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  2334. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  2335. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  2336. &lt;/span&gt;&lt;span class="lnt"&gt;10
  2337. &lt;/span&gt;&lt;span class="lnt"&gt;11
  2338. &lt;/span&gt;&lt;span class="lnt"&gt;12
  2339. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2340. &lt;td class="lntd"&gt;
  2341. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  2342. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;IsEncrypted&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2343. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Values&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2344. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;FUNCTIONS_WORKER_RUNTIME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2345. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;AzureWebJobsFeatureFlags&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;EnableWorkerIndexing&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2346. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;AzureWebJobsStorage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2347. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;KeyVaultUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://&amp;lt;key vault name&amp;gt;.vault.azure.net/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2348. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;CertificateName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cli-m365-cert&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2349. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;TenantId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;tenant-id&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2350. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;ClientId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;client-id&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2351. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  2352. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2353. &lt;/div&gt;
  2354. &lt;/div&gt;
  2355. &lt;/figure&gt;
  2356. &lt;blockquote class='important'&gt;
  2357. &lt;div class='mb-2'&gt;
  2358. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  2359. &lt;/div&gt;
  2360. &lt;p&gt;
  2361. &lt;span&gt;Make sure you replace the placeholders with your values&lt;/span&gt;
  2362. &lt;/p&gt;
  2363. &lt;/blockquote&gt;
  2364. &lt;h4 id="why-not-use-the-azure-key-vault-references"&gt;Why not use the Azure Key Vault references?&lt;/h4&gt;
  2365. &lt;p&gt;In the settings, we use the Azure Key Vault URL and certificate&amp;rsquo;s name to retrieve the certificate. Another way would be using key vault references like: &lt;code&gt;@Microsoft.KeyVault(SecretUri=&amp;lt;certificate identifier&amp;gt;)&lt;/code&gt;.&lt;/p&gt;
  2366. &lt;div class="caption my-4"&gt;
  2367. &lt;figure class="caption__figure"&gt;
  2368. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/key-vault-reference.webp" title="Show image"&gt;
  2369. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2370. &lt;img data-lqip="data:image/jpeg;base64,UklGRmQAAABXRUJQVlA4WAoAAAAQAAAACQAAAQAAQUxQSBMAAAABD6AQQADEX7XxiYgYrEBE/0MBAFZQOCAqAAAAsAEAnQEqCgACAAFAJiWkAALc/0uw0AD&amp;#43;/g7gGLRH5gPqkU34zSGgAAAA" src="https://www.eliostruyf.com/uploads/2024/03/key-vault-reference.webp" alt="Using the Azure Key Vault reference" style="width: 2228px;" class="lazyload" /&gt;
  2371. &lt;/a&gt;
  2372. &lt;figcaption class="caption__text"&gt;Using the Azure Key Vault reference&lt;/figcaption&gt;
  2373. &lt;/figure&gt;
  2374. &lt;/div&gt;
  2375. &lt;p&gt;The downside of this approach is that you have to store the certificate value in the &lt;code&gt;local.settings.json&lt;/code&gt; file, which is not recommended. By using the certificate name, we control everything in the Azure Key Vault.&lt;/p&gt;
  2376. &lt;h3 id="using-esm-modules-in-azure-functions"&gt;Using ESM modules in Azure Functions&lt;/h3&gt;
  2377. &lt;p&gt;As the latest version of CLI for Microsoft 365 uses ESM modules, we need to configure the Azure Functions project to use ESM modules.&lt;/p&gt;
  2378. &lt;p&gt;In the &lt;code&gt;package.json&lt;/code&gt; file, add the following line:&lt;/p&gt;
  2379. &lt;figure class="codeblock_titled "&gt;
  2380. &lt;figcaption class="codeblock_titled__header"&gt;package.json&lt;/figcaption&gt;
  2381. &lt;div class="highlight" title="package.json"&gt;&lt;div class="chroma"&gt;
  2382. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2383. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2384. &lt;/span&gt;&lt;span class="lnt"&gt;2
  2385. &lt;/span&gt;&lt;span class="lnt"&gt;3
  2386. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2387. &lt;td class="lntd"&gt;
  2388. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  2389. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;module&amp;#34;&lt;/span&gt;
  2390. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2391. &lt;/div&gt;
  2392. &lt;/div&gt;
  2393. &lt;/figure&gt;
  2394. &lt;p&gt;Update the &lt;code&gt;tsconfig.json&lt;/code&gt; file to the following:&lt;/p&gt;
  2395. &lt;figure class="codeblock_titled "&gt;
  2396. &lt;figcaption class="codeblock_titled__header"&gt;tsconfig.json&lt;/figcaption&gt;
  2397. &lt;div class="highlight" title="tsconfig.json"&gt;&lt;div class="chroma"&gt;
  2398. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2399. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  2400. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  2401. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  2402. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  2403. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  2404. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  2405. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  2406. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  2407. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  2408. &lt;/span&gt;&lt;span class="lnt"&gt;10
  2409. &lt;/span&gt;&lt;span class="lnt"&gt;11
  2410. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2411. &lt;td class="lntd"&gt;
  2412. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  2413. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;compilerOptions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2414. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;module&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ESNext&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2415. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;target&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ESNext&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2416. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;moduleResolution&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2417. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;outDir&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dist&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2418. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;rootDir&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2419. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sourceMap&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2420. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;strict&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  2421. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  2422. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2423. &lt;/div&gt;
  2424. &lt;/div&gt;
  2425. &lt;/figure&gt;
  2426. &lt;h3 id="create-a-new-azure-function"&gt;Create a new Azure Function&lt;/h3&gt;
  2427. &lt;p&gt;I created a new Azure Function using the following command:&lt;/p&gt;
  2428. &lt;figure class="codeblock_titled "&gt;
  2429. &lt;figcaption class="codeblock_titled__header"&gt;Create a new Azure Function&lt;/figcaption&gt;
  2430. &lt;div class="highlight" title="Create a new Azure Function"&gt;&lt;div class="chroma"&gt;
  2431. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2432. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2433. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2434. &lt;td class="lntd"&gt;
  2435. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;func new --name getSiteTitle --template &lt;span class="s2"&gt;&amp;#34;HTTP trigger&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2436. &lt;/div&gt;
  2437. &lt;/div&gt;
  2438. &lt;/figure&gt;
  2439. &lt;h3 id="implement-the-azure-function"&gt;Implement the Azure Function&lt;/h3&gt;
  2440. &lt;p&gt;Open the &lt;code&gt;./src/functions/getSiteTitle.ts&lt;/code&gt; file and replace the content with the following code:&lt;/p&gt;
  2441. &lt;figure class="codeblock_titled "&gt;
  2442. &lt;figcaption class="codeblock_titled__header"&gt;./src/functions/getSiteTitle.ts&lt;/figcaption&gt;
  2443. &lt;div class="highlight" title="./src/functions/getSiteTitle.ts"&gt;&lt;div class="chroma"&gt;
  2444. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2445. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  2446. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  2447. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  2448. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  2449. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  2450. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  2451. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  2452. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  2453. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  2454. &lt;/span&gt;&lt;span class="lnt"&gt;10
  2455. &lt;/span&gt;&lt;span class="lnt"&gt;11
  2456. &lt;/span&gt;&lt;span class="lnt"&gt;12
  2457. &lt;/span&gt;&lt;span class="lnt"&gt;13
  2458. &lt;/span&gt;&lt;span class="lnt"&gt;14
  2459. &lt;/span&gt;&lt;span class="lnt"&gt;15
  2460. &lt;/span&gt;&lt;span class="lnt"&gt;16
  2461. &lt;/span&gt;&lt;span class="lnt"&gt;17
  2462. &lt;/span&gt;&lt;span class="lnt"&gt;18
  2463. &lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;19
  2464. &lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;20
  2465. &lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;21
  2466. &lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;22
  2467. &lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;23
  2468. &lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;24
  2469. &lt;/span&gt;&lt;span class="lnt"&gt;25
  2470. &lt;/span&gt;&lt;span class="lnt"&gt;26
  2471. &lt;/span&gt;&lt;span class="lnt"&gt;27
  2472. &lt;/span&gt;&lt;span class="lnt"&gt;28
  2473. &lt;/span&gt;&lt;span class="lnt"&gt;29
  2474. &lt;/span&gt;&lt;span class="lnt"&gt;30
  2475. &lt;/span&gt;&lt;span class="lnt"&gt;31
  2476. &lt;/span&gt;&lt;span class="lnt"&gt;32
  2477. &lt;/span&gt;&lt;span class="lnt"&gt;33
  2478. &lt;/span&gt;&lt;span class="lnt"&gt;34
  2479. &lt;/span&gt;&lt;span class="lnt"&gt;35
  2480. &lt;/span&gt;&lt;span class="lnt"&gt;36
  2481. &lt;/span&gt;&lt;span class="lnt"&gt;37
  2482. &lt;/span&gt;&lt;span class="lnt"&gt;38
  2483. &lt;/span&gt;&lt;span class="lnt"&gt;39
  2484. &lt;/span&gt;&lt;span class="lnt"&gt;40
  2485. &lt;/span&gt;&lt;span class="lnt"&gt;41
  2486. &lt;/span&gt;&lt;span class="lnt"&gt;42
  2487. &lt;/span&gt;&lt;span class="lnt"&gt;43
  2488. &lt;/span&gt;&lt;span class="lnt"&gt;44
  2489. &lt;/span&gt;&lt;span class="lnt"&gt;45
  2490. &lt;/span&gt;&lt;span class="lnt"&gt;46
  2491. &lt;/span&gt;&lt;span class="lnt"&gt;47
  2492. &lt;/span&gt;&lt;span class="lnt"&gt;48
  2493. &lt;/span&gt;&lt;span class="lnt"&gt;49
  2494. &lt;/span&gt;&lt;span class="lnt"&gt;50
  2495. &lt;/span&gt;&lt;span class="lnt"&gt;51
  2496. &lt;/span&gt;&lt;span class="lnt"&gt;52
  2497. &lt;/span&gt;&lt;span class="lnt"&gt;53
  2498. &lt;/span&gt;&lt;span class="lnt"&gt;54
  2499. &lt;/span&gt;&lt;span class="lnt"&gt;55
  2500. &lt;/span&gt;&lt;span class="lnt"&gt;56
  2501. &lt;/span&gt;&lt;span class="lnt"&gt;57
  2502. &lt;/span&gt;&lt;span class="lnt"&gt;58
  2503. &lt;/span&gt;&lt;span class="lnt"&gt;59
  2504. &lt;/span&gt;&lt;span class="lnt"&gt;60
  2505. &lt;/span&gt;&lt;span class="lnt"&gt;61
  2506. &lt;/span&gt;&lt;span class="lnt"&gt;62
  2507. &lt;/span&gt;&lt;span class="lnt"&gt;63
  2508. &lt;/span&gt;&lt;span class="lnt"&gt;64
  2509. &lt;/span&gt;&lt;span class="lnt"&gt;65
  2510. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2511. &lt;td class="lntd"&gt;
  2512. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HttpResponseInit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;InvocationContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@azure/functions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2513. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AzureCliCredential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ChainedTokenCredential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TokenCredential&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@azure/identity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2514. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SecretClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@azure/keyvault-secrets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2515. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;executeCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pnp/cli-microsoft365&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2516. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2517. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getSiteTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;: &lt;span class="kt"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;__&lt;/span&gt;: &lt;span class="kt"&gt;InvocationContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;HttpResponseInit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2518. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyVaultUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KeyVaultUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2519. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;certificateName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CertificateName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2520. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TenantId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2521. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ClientId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  2522. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2523. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;keyVaultUrl&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;certificateName&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2524. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2525. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;status&lt;/span&gt;: &lt;span class="kt"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2526. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Missing environment variables&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2527. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  2528. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  2529. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2530. &lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;creds&lt;/span&gt;: &lt;span class="kt"&gt;TokenCredential&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  2531. &lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  2532. &lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AzureCliCredential&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  2533. &lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;];&lt;/span&gt;
  2534. &lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credentialChain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ChainedTokenCredential&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2535. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2536. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SecretClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyVaultUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;credentialChain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2537. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;certificate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;certificateName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2538. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2539. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;certificate&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2540. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2541. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;status&lt;/span&gt;: &lt;span class="kt"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2542. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Certificate not found&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2543. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  2544. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  2545. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2546. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;login&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2547. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;interactive&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2548. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;authType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;certificate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2549. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt;: &lt;span class="kt"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2550. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;appId&lt;/span&gt;: &lt;span class="kt"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2551. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;certificateBase64Encoded&lt;/span&gt;: &lt;span class="kt"&gt;certificate.value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2552. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  2553. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2554. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2555. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2556. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;status&lt;/span&gt;: &lt;span class="kt"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2557. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`Error logging in: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2558. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  2559. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  2560. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2561. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;spo web get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2562. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://struyfconsultingdev.sharepoint.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2563. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  2564. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;siteInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  2565. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2566. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2567. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;status&lt;/span&gt;: &lt;span class="kt"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2568. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;jsonBody&lt;/span&gt;: &lt;span class="kt"&gt;siteInfo.Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2569. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  2570. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  2571. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2572. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;getSiteTitle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  2573. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  2574. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;authLevel&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2575. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;: &lt;span class="kt"&gt;getSiteTitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  2576. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2577. &lt;/div&gt;
  2578. &lt;/div&gt;
  2579. &lt;/figure&gt;
  2580. &lt;p&gt;The Azure Function will authenticate with the CLI for Microsoft 365 using the certificate-based authentication and retrieve the title of the specified site.&lt;/p&gt;
  2581. &lt;blockquote class='info'&gt;
  2582. &lt;div class='mb-2'&gt;
  2583. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  2584. &lt;/div&gt;
  2585. &lt;p&gt;
  2586. &lt;span&gt;In the code, we make use of &lt;code&gt;executeCommand&lt;/code&gt; from CLI for Microsoft 365. This allows us to call any of the CLI commands. You can read more on the &lt;a href="https://pnp.github.io/cli-microsoft365/user-guide/use-cli-api"&gt;use CLI for Microsoft 365 programmatically&lt;/a&gt; documentation.&lt;/span&gt;
  2587. &lt;/p&gt;
  2588. &lt;/blockquote&gt;
  2589. &lt;h3 id="locally-running-the-azure-function"&gt;Locally running the Azure Function&lt;/h3&gt;
  2590. &lt;p&gt;To run the Azure Function, we need to be able to authenticate with the Azure Key Vault to fetch the certificate. On Azure, we will use a managed identity, but locally, we can use the Azure CLI authentication. The great thing about the &lt;code&gt;@azure/identity&lt;/code&gt; package is that you can chain credentials, which allows you to use multiple authentication methods. The first one that succeeds will be used.&lt;/p&gt;
  2591. &lt;p&gt;In the above sample, you can see the following credentials:&lt;/p&gt;
  2592. &lt;ul&gt;
  2593. &lt;li&gt;&lt;code&gt;DefaultAzureCredential&lt;/code&gt;: This credential will be used for the managed identity on Azure&lt;/li&gt;
  2594. &lt;li&gt;&lt;code&gt;AzureCliCredential&lt;/code&gt;: This credential will be used for the Azure CLI authentication&lt;/li&gt;
  2595. &lt;/ul&gt;
  2596. &lt;p&gt;To run the Azure Function locally, you first have to sign in to the Azure CLI:&lt;/p&gt;
  2597. &lt;figure class="codeblock_titled "&gt;
  2598. &lt;figcaption class="codeblock_titled__header"&gt;Sign in to the Azure CLI&lt;/figcaption&gt;
  2599. &lt;div class="highlight" title="Sign in to the Azure CLI"&gt;&lt;div class="chroma"&gt;
  2600. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2601. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2602. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2603. &lt;td class="lntd"&gt;
  2604. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;az login&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2605. &lt;/div&gt;
  2606. &lt;/div&gt;
  2607. &lt;/figure&gt;
  2608. &lt;blockquote class='important'&gt;
  2609. &lt;div class='mb-2'&gt;
  2610. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  2611. &lt;/div&gt;
  2612. &lt;p&gt;
  2613. &lt;span&gt;Be sure to login into the Azure environment where you configured the Azure Key vault.&lt;/span&gt;
  2614. &lt;/p&gt;
  2615. &lt;/blockquote&gt;
  2616. &lt;p&gt;Once logged in, you can run the Azure Function using the following command or press &lt;code&gt;F5&lt;/code&gt; in Visual Studio Code:&lt;/p&gt;
  2617. &lt;figure class="codeblock_titled "&gt;
  2618. &lt;figcaption class="codeblock_titled__header"&gt;Build and run the Azure Function&lt;/figcaption&gt;
  2619. &lt;div class="highlight" title="Build and run the Azure Function"&gt;&lt;div class="chroma"&gt;
  2620. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2621. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2622. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2623. &lt;td class="lntd"&gt;
  2624. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; func start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2625. &lt;/div&gt;
  2626. &lt;/div&gt;
  2627. &lt;/figure&gt;
  2628. &lt;p&gt;The Azure Function will start, and you can navigate to &lt;code&gt;http://localhost:7071/api/getSiteTitle&lt;/code&gt; to see the result.&lt;/p&gt;
  2629. &lt;div class="caption my-4"&gt;
  2630. &lt;figure class="caption__figure"&gt;
  2631. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/azure-function-outcome.webp" title="Show image"&gt;
  2632. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2633. &lt;img data-lqip="data:image/jpeg;base64,UklGRoAAAABXRUJQVlA4WAoAAAAQAAAACQAAAgAAQUxQSB8AAAAAe7i4uLi4uLi4e7L//////////7J7uLi4uLi4uLh7AFZQOCA6AAAAUAIAnQEqCgADAAFAJiWUAnRrgLf/A9EKHlsAAP7773KiPgS4E0HjCvTtdfBnXFys/GxOsskGtV&amp;#43;gAA==" src="https://www.eliostruyf.com/uploads/2024/03/azure-function-outcome.webp" alt="The title of the site as a result of executing the Azure Function" style="width: 660px;" class="lazyload" /&gt;
  2634. &lt;/a&gt;
  2635. &lt;figcaption class="caption__text"&gt;The title of the site as a result of executing the Azure Function&lt;/figcaption&gt;
  2636. &lt;/figure&gt;
  2637. &lt;/div&gt;
  2638. &lt;h3 id="running-the-azure-function-on-azure"&gt;Running the Azure Function on Azure&lt;/h3&gt;
  2639. &lt;blockquote class='important'&gt;
  2640. &lt;div class='mb-2'&gt;
  2641. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  2642. &lt;/div&gt;
  2643. &lt;p&gt;
  2644. &lt;span&gt;I will not go into detail on how to deploy the Azure Function to Azure.&lt;/span&gt;
  2645. &lt;/p&gt;
  2646. &lt;/blockquote&gt;
  2647. &lt;p&gt;Assuming you have published the code to your Azure Functions, you must perform some configuration steps to make the function work.&lt;/p&gt;
  2648. &lt;h4 id="managed-identity"&gt;Managed Identity&lt;/h4&gt;
  2649. &lt;p&gt;First, you must configure the managed identity for the Azure Function. This managed identity will be used to authenticate with the Azure Key Vault and retrieve the certificate.&lt;/p&gt;
  2650. &lt;p&gt;Open the Azure Function in the Azure portal and follow the steps below:&lt;/p&gt;
  2651. &lt;ul&gt;
  2652. &lt;li&gt;Navigate to the &lt;code&gt;Identity&lt;/code&gt; tab&lt;/li&gt;
  2653. &lt;li&gt;Enable the &lt;code&gt;System assigned&lt;/code&gt; identity&lt;/li&gt;
  2654. &lt;/ul&gt;
  2655. &lt;div class="caption my-4"&gt;
  2656. &lt;figure class="caption__figure"&gt;
  2657. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/enable-system-assigned.webp" title="Show image"&gt;
  2658. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2659. &lt;img data-lqip="data:image/jpeg;base64,UklGRnIAAABXRUJQVlA4WAoAAAAQAAAACQAAAwAAQUxQSCAAAAABH0CQbePvuf0Y04iIiA41AQgw1q1/BgpH9D&amp;#43;&amp;#43;EiHyFVZQOCAsAAAAsAEAnQEqCgAEAAFAJiWkAALnYdvXgAD&amp;#43;/ZYh3RD5ZN7DPocsyZrgC/kAAAA=" src="https://www.eliostruyf.com/uploads/2024/03/enable-system-assigned.webp" alt="Enable the system assigned managed identity" style="width: 1852px;" class="lazyload" /&gt;
  2660. &lt;/a&gt;
  2661. &lt;figcaption class="caption__text"&gt;Enable the system assigned managed identity&lt;/figcaption&gt;
  2662. &lt;/figure&gt;
  2663. &lt;/div&gt;
  2664. &lt;blockquote class='info'&gt;
  2665. &lt;div class='mb-2'&gt;
  2666. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  2667. &lt;/div&gt;
  2668. &lt;p&gt;
  2669. &lt;span&gt;You can also use &lt;code&gt;user assigned&lt;/code&gt;, but for this kind of Managed Identity, you first need to create a User Managed Identity and make use of the &lt;code&gt;ManagedIdentityCredential&lt;/code&gt; credential from the Azure Identity dependency.&lt;/span&gt;
  2670. &lt;/p&gt;
  2671. &lt;/blockquote&gt;
  2672. &lt;h4 id="key-vault-access-policy"&gt;Key Vault access policy&lt;/h4&gt;
  2673. &lt;p&gt;Next, you must configure the access policy for the managed identity on the Azure Key Vault. Follow the steps below:&lt;/p&gt;
  2674. &lt;ul&gt;
  2675. &lt;li&gt;Navigate to the Azure Key Vault&lt;/li&gt;
  2676. &lt;li&gt;Click on &lt;code&gt;Access policies&lt;/code&gt; in the left menu&lt;/li&gt;
  2677. &lt;li&gt;Click on &lt;code&gt;Create&lt;/code&gt; in the top menu&lt;/li&gt;
  2678. &lt;li&gt;Select the following permissions:
  2679. &lt;ul&gt;
  2680. &lt;li&gt;&lt;code&gt;Get&lt;/code&gt; for &lt;code&gt;Secret permissions&lt;/code&gt;&lt;/li&gt;
  2681. &lt;li&gt;&lt;code&gt;Get&lt;/code&gt; for &lt;code&gt;Certificate permissions&lt;/code&gt;&lt;/li&gt;
  2682. &lt;/ul&gt;
  2683. &lt;/li&gt;
  2684. &lt;li&gt;Click on &lt;code&gt;next&lt;/code&gt;&lt;/li&gt;
  2685. &lt;li&gt;Search for your Azure Function by its name or use the object ID&lt;/li&gt;
  2686. &lt;li&gt;Click on &lt;code&gt;next&lt;/code&gt; and &lt;code&gt;create&lt;/code&gt;&lt;/li&gt;
  2687. &lt;/ul&gt;
  2688. &lt;div class="caption my-4"&gt;
  2689. &lt;figure class="caption__figure"&gt;
  2690. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/key-vault-access-policy.webp" title="Show image"&gt;
  2691. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2692. &lt;img data-lqip="data:image/jpeg;base64,UklGRl4AAABXRUJQVlA4WAoAAAAQAAAACQAAAQAAQUxQSBQAAAABD6AgbQPGv8x2R1NEBMcKRPQ/FFZQOCAkAAAAsAEAnQEqCgACAAFAJiWkAAMX/EKdQAD&amp;#43;/PzJOKVu31MAAAAA" src="https://www.eliostruyf.com/uploads/2024/03/key-vault-access-policy.webp" alt="Assigned the Azure Function - Managed Identity to the Azure Key Vault" style="width: 1792px;" class="lazyload" /&gt;
  2693. &lt;/a&gt;
  2694. &lt;figcaption class="caption__text"&gt;Assigned the Azure Function - Managed Identity to the Azure Key Vault&lt;/figcaption&gt;
  2695. &lt;/figure&gt;
  2696. &lt;/div&gt;
  2697. &lt;h4 id="azure-function-configuration"&gt;Azure Function configuration&lt;/h4&gt;
  2698. &lt;p&gt;Finally, you will need to configure the Azure Functions settings. Follow the steps below:&lt;/p&gt;
  2699. &lt;ul&gt;
  2700. &lt;li&gt;Navigate to the Azure Function&lt;/li&gt;
  2701. &lt;li&gt;Click on &lt;code&gt;Configuration&lt;/code&gt; in the left menu&lt;/li&gt;
  2702. &lt;li&gt;Add the following settings:
  2703. &lt;ul&gt;
  2704. &lt;li&gt;&lt;code&gt;KeyVaultUrl&lt;/code&gt;: The URL of the Azure Key Vault&lt;/li&gt;
  2705. &lt;li&gt;&lt;code&gt;CertificateName&lt;/code&gt;: The name of the certificate in the Azure Key Vault&lt;/li&gt;
  2706. &lt;li&gt;&lt;code&gt;TenantId&lt;/code&gt;: The ID of your Microsoft 365 tenant. The tenant ID can be found on the Microsoft Entra ID overview page.&lt;/li&gt;
  2707. &lt;li&gt;&lt;code&gt;ClientId&lt;/code&gt;: The Application (client) ID of the App registration you created&lt;/li&gt;
  2708. &lt;/ul&gt;
  2709. &lt;/li&gt;
  2710. &lt;/ul&gt;
  2711. &lt;p&gt;When you save the settings, the Azure Function can authenticate with the Azure Key Vault and retrieve the certificate.&lt;/p&gt;
  2712. &lt;div class="caption my-4"&gt;
  2713. &lt;figure class="caption__figure"&gt;
  2714. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/03/azure-function-api.png" title="Show image"&gt;
  2715. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2716. &lt;img data-lqip="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAACCAYAAABhYU3QAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAVElEQVR4nGOYG2GxYmqQ4ZOpAfq3pgUb35kaaHAHRE8LMrwzLdgIJPZsir/eYgZPI83VtvoaL611Ve9ZaKs80JOXfKArJw7B8hL3dOXEX&amp;#43;nJiS8FAKq5IECvqUyGAAAAAElFTkSuQmCC" src="https://www.eliostruyf.com/uploads/2024/03/azure-function-api.png" alt="Calling the Azure Function on Azure" style="width: 884px;" class="lazyload" /&gt;
  2717. &lt;/a&gt;
  2718. &lt;figcaption class="caption__text"&gt;Calling the Azure Function on Azure&lt;/figcaption&gt;
  2719. &lt;/figure&gt;
  2720. &lt;/div&gt;
  2721. &lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
  2722. &lt;p&gt;In this article, I showed you how to use the CLI for Microsoft 365 in TypeScript Azure Functions. We configured the certificate-based authentication and used the Azure Key Vault to store the certificate.&lt;/p&gt;
  2723. &lt;p&gt;Be sure to read the &lt;a href="https://pnp.github.io/cli-microsoft365/user-guide/cli-certificate-caveats"&gt;caveats when working with the CLI and certificate login&lt;/a&gt; to understand the limitations of this approach.&lt;/p&gt;</content:encoded></item><item><title>Add a support link for a VSCode extension on the marketplace</title><link>https://www.eliostruyf.com/add-support-link-vscode-extension-marketplace/</link><pubDate>Wed, 28 Feb 2024 14:19:19 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/add-support-link-vscode-extension-marketplace/</guid><description>While preparing a new release for Front Matter CMS, I noticed an API call to GitHub failing from the Visual Studio Code marketplace. While looking into it, it requested a SUPPORT.md file in the repository&amp;rsquo;s root.
  2724. Show image VSCode Marketplace - Call for the SUPPORT.md file The VSCode Marketplace uses the following API format: https://api.</description><content:encoded>&lt;p&gt;While preparing a new release for Front Matter CMS, I noticed an API call to GitHub failing from the Visual Studio Code marketplace. While looking into it, it requested a &lt;code&gt;SUPPORT.md&lt;/code&gt; file in the repository&amp;rsquo;s root.&lt;/p&gt;
  2725. &lt;div class="caption my-4"&gt;
  2726. &lt;figure class="caption__figure"&gt;
  2727. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/vscode-marketplace-support.webp" title="Show image"&gt;
  2728. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2729. &lt;img data-lqip="data:image/jpeg;base64,UklGRoYAAABXRUJQVlA4WAoAAAAQAAAACQAABAAAQUxQSCAAAAABHyCQTfxNWxLXiIiQoSYAEobhTf8MUNMQEf2PNcT9NVZQOCBAAAAA0AEAnQEqCgAFAAFAJiWcAvzhC1zyLAAA/ub7pyDo&amp;#43;lXcK37piW30q1PIFa5h//83WyZ/RRbeSoopxDOGR4AAAA==" src="https://www.eliostruyf.com/uploads/2024/02/vscode-marketplace-support.webp" alt="VSCode Marketplace - Call for the SUPPORT.md file" style="width: 1200px;" class="lazyload" /&gt;
  2730. &lt;/a&gt;
  2731. &lt;figcaption class="caption__text"&gt;VSCode Marketplace - Call for the SUPPORT.md file&lt;/figcaption&gt;
  2732. &lt;/figure&gt;
  2733. &lt;/div&gt;
  2734. &lt;p&gt;The VSCode Marketplace uses the following API format: &lt;code&gt;https://api.github.com/repos/username/repo/contents/SUPPORT.md&lt;/code&gt;.&lt;/p&gt;
  2735. &lt;blockquote class='info'&gt;
  2736. &lt;div class='mb-2'&gt;
  2737. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  2738. &lt;/div&gt;
  2739. &lt;p&gt;
  2740. &lt;span&gt;You can find more information in the &lt;a href="https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content"&gt;get repository content&lt;/a&gt; GitHub API documentation.&lt;/span&gt;
  2741. &lt;/p&gt;
  2742. &lt;/blockquote&gt;
  2743. &lt;p&gt;This API returns more information about the file if it exists, like the &lt;code&gt;download_url&lt;/code&gt; and &lt;code&gt;html_url&lt;/code&gt;. The &lt;code&gt;html_url&lt;/code&gt; links to the support page from the marketplace.&lt;/p&gt;
  2744. &lt;p&gt;If you want to add a support link to your extension under the &lt;strong&gt;resources&lt;/strong&gt; section. All you have to do is a &lt;code&gt;SUPPORT.md&lt;/code&gt; file in the root of your repository. The content of the file can be anything you want. Once you push the file to the repository, the marketplace picks it up and displays the support link.&lt;/p&gt;
  2745. &lt;div class="caption my-4"&gt;
  2746. &lt;figure class="caption__figure"&gt;
  2747. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/vscode-marketplace-support-link.webp" title="Show image"&gt;
  2748. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2749. &lt;img data-lqip="data:image/jpeg;base64,UklGRooAAABXRUJQVlA4WAoAAAAQAAAACQAABAAAQUxQSCEAAAABHyAgIf4/lRHciIiYhJoASBiGN/0zQE1DRPQ/1hD31wAAVlA4IEIAAADQAQCdASoKAAUAAUAmJaQAAunBDF96gAD&amp;#43;6l1pR3ZvZdYprft8xvULi3B4At//LD43f3wXyDqd1URXyGLqz2GAAAA=" src="https://www.eliostruyf.com/uploads/2024/02/vscode-marketplace-support-link.webp" alt="VSCode Marketplace - Support link" style="width: 1200px;" class="lazyload" /&gt;
  2750. &lt;/a&gt;
  2751. &lt;figcaption class="caption__text"&gt;VSCode Marketplace - Support link&lt;/figcaption&gt;
  2752. &lt;/figure&gt;
  2753. &lt;/div&gt;</content:encoded></item><item><title>Running .NET Azure Functions on macOS and Visual Studio Code</title><link>https://www.eliostruyf.com/running-net-azure-functions-macos-visual-studio-code/</link><pubDate>Tue, 27 Feb 2024 13:18:52 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/running-net-azure-functions-macos-visual-studio-code/</guid><description>TypeScript is typically my go-to language for building any solution, but sometimes, you must use what is best for the job. In my current project, I am using .NET Core to build Azure Functions, and I had to get myself familiar with using .Net Core and Azure Functions on macOS.</description><content:encoded>&lt;p&gt;TypeScript is typically my go-to language for building any solution, but sometimes, you must use what is best for the job. In my current project, I am using .NET Core to build Azure Functions, and I had to get myself familiar with using .Net Core and Azure Functions on macOS.&lt;/p&gt;
  2754. &lt;p&gt;As the Microsoft documentation only explained it with Visual Studio for Mac, I had to figure out how to get it working with Visual Studio Code. This post will describe getting .NET Core and Azure Functions working on macOS with Visual Studio Code.&lt;/p&gt;
  2755. &lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
  2756. &lt;p&gt;To start building. NET-based Azure Functions, you need to install the .NET SDK and Azure Functions Core Tools on your machine.&lt;/p&gt;
  2757. &lt;h3 id="installing-net-sdk-on-macos"&gt;Installing .NET SDK on macOS&lt;/h3&gt;
  2758. &lt;p&gt;The .NET-supported versions are found on the following &lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet"&gt;download .NET&lt;/a&gt; page.&lt;/p&gt;
  2759. &lt;p&gt;I went for the binaries of the .NET 8.0 SDK for macOS &lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet/8.0"&gt;download .NET 8.0.201 SDK for macOS Arm64&lt;/a&gt;.&lt;/p&gt;
  2760. &lt;p&gt;If you go for the binary download, once downloaded, you can move the files from the tar file to the directory of your choice. On the .NET download page, they recommend using the &lt;code&gt;$HOME/dotnet&lt;/code&gt; directory. In my case, I moved the files to the &lt;code&gt;$HOME/.dotnet&lt;/code&gt; directory.&lt;/p&gt;
  2761. &lt;figure class="codeblock_titled "&gt;
  2762. &lt;figcaption class="codeblock_titled__header"&gt;Moving the .NET SDK files to the .dotnet directory&lt;/figcaption&gt;
  2763. &lt;div class="highlight" title="Moving the .NET SDK files to the .dotnet directory"&gt;&lt;div class="chroma"&gt;
  2764. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2765. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2766. &lt;/span&gt;&lt;span class="lnt"&gt;2
  2767. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2768. &lt;td class="lntd"&gt;
  2769. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Downloads
  2770. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.dotnet &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; tar zxf dotnet-sdk-8.0.201-osx-arm64.tar.gz -C &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.dotnet&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2771. &lt;/div&gt;
  2772. &lt;/div&gt;
  2773. &lt;/figure&gt;
  2774. &lt;blockquote class='important'&gt;
  2775. &lt;div class='mb-2'&gt;
  2776. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  2777. &lt;/div&gt;
  2778. &lt;p&gt;
  2779. &lt;span&gt;The filename can vary depending on which version you downloaded.&lt;/span&gt;
  2780. &lt;/p&gt;
  2781. &lt;/blockquote&gt;
  2782. &lt;p&gt;After moving the files, you must add the .NET SDK to your shell profile or any other profile you use.&lt;/p&gt;
  2783. &lt;p&gt;In my case, I am using &lt;code&gt;zsh&lt;/code&gt;, so I added the following to my &lt;code&gt;.zshrc&lt;/code&gt; file and the &lt;code&gt;.zprofile&lt;/code&gt; file.&lt;/p&gt;
  2784. &lt;ul&gt;
  2785. &lt;li&gt;Shell profile: &lt;code&gt;~/.profile&lt;/code&gt;&lt;/li&gt;
  2786. &lt;li&gt;Bash profile: &lt;code&gt;~/.bash_profile&lt;/code&gt;, &lt;code&gt;~/.bashrc&lt;/code&gt;&lt;/li&gt;
  2787. &lt;li&gt;Zsh profile: &lt;code&gt;~/.zshrc&lt;/code&gt;, &lt;code&gt;~/.zprofile&lt;/code&gt;&lt;/li&gt;
  2788. &lt;/ul&gt;
  2789. &lt;p&gt;In those files, you need to add the following lines:&lt;/p&gt;
  2790. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  2791. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2792. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2793. &lt;/span&gt;&lt;span class="lnt"&gt;2
  2794. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2795. &lt;td class="lntd"&gt;
  2796. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;DOTNET_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.dotnet
  2797. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.dotnet&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2798. &lt;/div&gt;
  2799. &lt;/div&gt;
  2800. &lt;blockquote class='info'&gt;
  2801. &lt;div class='mb-2'&gt;
  2802. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  2803. &lt;/div&gt;
  2804. &lt;p&gt;
  2805. &lt;span&gt;By adding those lines, you permanently make &lt;code&gt;dotnet&lt;/code&gt; commands available in your terminal.&lt;/span&gt;
  2806. &lt;/p&gt;
  2807. &lt;/blockquote&gt;
  2808. &lt;p&gt;After adding it, restart your terminal or run the following commands to apply the changes:&lt;/p&gt;
  2809. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  2810. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2811. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2812. &lt;/span&gt;&lt;span class="lnt"&gt;2
  2813. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2814. &lt;td class="lntd"&gt;
  2815. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Change the file to the profile you are using&lt;/span&gt;
  2816. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2817. &lt;/div&gt;
  2818. &lt;/div&gt;
  2819. &lt;p&gt;Once your profile has been reloaded, you can check if the .NET SDK is installed by running the following command:&lt;/p&gt;
  2820. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  2821. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2822. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2823. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2824. &lt;td class="lntd"&gt;
  2825. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet --info&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2826. &lt;/div&gt;
  2827. &lt;/div&gt;
  2828. &lt;h3 id="installing-azure-functions-core-tools"&gt;Installing Azure Functions Core Tools&lt;/h3&gt;
  2829. &lt;p&gt;The documentation for installing the Azure Functions Core Tools on macOS is up to date. You can find it in the &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=macos#install-the-azure-functions-core-tools"&gt;Install the Azure Functions Core Tools&lt;/a&gt; section of the Core Tools Development documentation.&lt;/p&gt;
  2830. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  2831. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2832. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2833. &lt;/span&gt;&lt;span class="lnt"&gt;2
  2834. &lt;/span&gt;&lt;span class="lnt"&gt;3
  2835. &lt;/span&gt;&lt;span class="lnt"&gt;4
  2836. &lt;/span&gt;&lt;span class="lnt"&gt;5
  2837. &lt;/span&gt;&lt;span class="lnt"&gt;6
  2838. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2839. &lt;td class="lntd"&gt;
  2840. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# When Homebrew is installed&lt;/span&gt;
  2841. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew tap azure/functions
  2842. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install azure-functions-core-tools@4
  2843. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  2844. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# To update the Azure Functions Core Tools&lt;/span&gt;
  2845. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew upgrade azure-functions-core-tools@4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2846. &lt;/div&gt;
  2847. &lt;/div&gt;
  2848. &lt;p&gt;To check if the Azure Functions Core Tools are installed, you can run the following command:&lt;/p&gt;
  2849. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  2850. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2851. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2852. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2853. &lt;td class="lntd"&gt;
  2854. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;func --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2855. &lt;/div&gt;
  2856. &lt;/div&gt;
  2857. &lt;h2 id="creating-a-new-azure-functions-project"&gt;Creating a new Azure Functions project&lt;/h2&gt;
  2858. &lt;p&gt;You can create a new Azure Functions project after installing the .NET SDK and Azure Functions Core Tools. You have the option of creating a new project from your terminal or Visual Studio Code with the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions"&gt;Azure Functions extension&lt;/a&gt;.&lt;/p&gt;
  2859. &lt;h3 id="using-the-terminal"&gt;Using the terminal&lt;/h3&gt;
  2860. &lt;p&gt;To create a new Azure Functions project, you can use the following command:&lt;/p&gt;
  2861. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  2862. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2863. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2864. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2865. &lt;td class="lntd"&gt;
  2866. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;func init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2867. &lt;/div&gt;
  2868. &lt;/div&gt;
  2869. &lt;p&gt;This command will ask you a few questions to set up the project. You can choose the following options:&lt;/p&gt;
  2870. &lt;ul&gt;
  2871. &lt;li&gt;Select the runtime: &lt;code&gt;dotnet (isolated process)&lt;/code&gt;&lt;/li&gt;
  2872. &lt;li&gt;Select the language: &lt;code&gt;C#&lt;/code&gt;&lt;/li&gt;
  2873. &lt;/ul&gt;
  2874. &lt;p&gt;If everything is installed correctly, it should create a new Azure Functions project in the current directory.&lt;/p&gt;
  2875. &lt;p&gt;New Azure Functions can be added to the project by running the following command:&lt;/p&gt;
  2876. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  2877. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2878. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2879. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2880. &lt;td class="lntd"&gt;
  2881. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;func new&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2882. &lt;/div&gt;
  2883. &lt;/div&gt;
  2884. &lt;h3 id="using-visual-studio-code"&gt;Using Visual Studio Code&lt;/h3&gt;
  2885. &lt;p&gt;Suppose you have installed the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions"&gt;Azure Functions extension&lt;/a&gt; in Visual Studio Code. In that case, you can create a new Azure Functions project by clicking on the Azure icon in the sidebar and then clicking the &lt;code&gt;Create New Project&lt;/code&gt; button in the workspace section.&lt;/p&gt;
  2886. &lt;div class="caption my-4"&gt;
  2887. &lt;figure class="caption__figure"&gt;
  2888. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/azure-function-creation.png" title="Show image"&gt;
  2889. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2890. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAADCAYAAACqPZ51AAAAAklEQVR4AewaftIAAABtSURBVC3BPQrCMACA0S8mrRWaoHsdFHESb&amp;#43;9thHoAl6SQhPy6&amp;#43;J44Xx8vrc3NOUcpmUEpzNEwa03eAlMfoNW3SklcrI13xIhUI6U19PJkPx3Ic8SGRNq&amp;#43;VdVW15a75K93kPbDLkhOdIIP&amp;#43;OjXHzIpMwU8nzyQAAAAAElFTkSuQmCC" src="https://www.eliostruyf.com/uploads/2024/02/azure-function-creation.png" alt="Create a new Azure Function project" style="width: 750px;" class="lazyload" /&gt;
  2891. &lt;/a&gt;
  2892. &lt;figcaption class="caption__text"&gt;Create a new Azure Function project&lt;/figcaption&gt;
  2893. &lt;/figure&gt;
  2894. &lt;/div&gt;
  2895. &lt;p&gt;It will ask you similar questions as the terminal command to set up the project.&lt;/p&gt;
  2896. &lt;div class="caption my-4"&gt;
  2897. &lt;figure class="caption__figure"&gt;
  2898. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/azure-functions-runtime.png" title="Show image"&gt;
  2899. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2900. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAADCAYAAACqPZ51AAAAAklEQVR4AewaftIAAABZSURBVG3BMQqAIBgG0M9&amp;#43;UyHqAkHR0gG6/9RNGlqrRVPQj2iN3lPTvKwi1UgSJOGcg4LCHSNI4lVK2bSI7a11w3FeyLnAB48vklmbWvaubbSpBT4E/Ekp7g9PpCYC5c8cZQAAAABJRU5ErkJggg==" src="https://www.eliostruyf.com/uploads/2024/02/azure-functions-runtime.png" alt="Select the Azure Functions runtime" style="width: 750px;" class="lazyload" /&gt;
  2901. &lt;/a&gt;
  2902. &lt;figcaption class="caption__text"&gt;Select the Azure Functions runtime&lt;/figcaption&gt;
  2903. &lt;/figure&gt;
  2904. &lt;/div&gt;
  2905. &lt;h2 id="running-the-azure-functions"&gt;Running the Azure Functions&lt;/h2&gt;
  2906. &lt;p&gt;Now that you have created the Azure Functions project, you can run the Azure Functions locally. Start by opening the project in Visual Studio Code if you haven&amp;rsquo;t already. It should suggest installing the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp"&gt;C# extension&lt;/a&gt;, which is required to debug the Azure Functions.&lt;/p&gt;
  2907. &lt;blockquote class='info'&gt;
  2908. &lt;div class='mb-2'&gt;
  2909. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  2910. &lt;/div&gt;
  2911. &lt;p&gt;
  2912. &lt;span&gt;Additionally, you can install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit"&gt;C# Dev Kit&lt;/a&gt;.&lt;/span&gt;
  2913. &lt;/p&gt;
  2914. &lt;/blockquote&gt;
  2915. &lt;p&gt;You only need to press &lt;code&gt;F5&lt;/code&gt; to debug the Azure Functions. It will start the Azure Functions Core Tools and run the Azure Functions locally.&lt;/p&gt;
  2916. &lt;div class="caption my-4"&gt;
  2917. &lt;figure class="caption__figure"&gt;
  2918. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/azure-functions-debugging.png" title="Show image"&gt;
  2919. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2920. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAHCAYAAAAxrNxjAAAAAklEQVR4AewaftIAAAC3SURBVD3BTU7DQAyA0c&amp;#43;en4RKQCuxZMWCLfc/DUcgkUjLzNgeuoH35PXt41O0nPs4WEvBNSPh9HYjiWDmRMwt&amp;#43;4hnUT/PEG4/nfEA04zav3EcG4O7mSUmIwwPhwSlFtwa5gPC&amp;#43;ZNbDEQUlcnLRaAaXhL7FSz4l1UVlYQofG2DoZ0AIj2BD0qt5KTkZVlYlxNpVbbrgakyubMFSqe3Ru&amp;#43;N/H563JeKHMeVywwaAqpozYRMRCYxZP8F8v5jEI3Pp2IAAAAASUVORK5CYII=" src="https://www.eliostruyf.com/uploads/2024/02/azure-functions-debugging.png" alt="Debugging the Azure Functions" style="width: 1200px;" class="lazyload" /&gt;
  2921. &lt;/a&gt;
  2922. &lt;figcaption class="caption__text"&gt;Debugging the Azure Functions&lt;/figcaption&gt;
  2923. &lt;/figure&gt;
  2924. &lt;/div&gt;
  2925. &lt;h2 id="extra-installing-azurite-for-local-development"&gt;Extra: Installing Azurite for local development&lt;/h2&gt;
  2926. &lt;p&gt;Azurite is a local emulator for Azure Storage. You can install Azurite with the following command:&lt;/p&gt;
  2927. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  2928. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2929. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2930. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2931. &lt;td class="lntd"&gt;
  2932. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install -g azurite&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2933. &lt;/div&gt;
  2934. &lt;/div&gt;
  2935. &lt;p&gt;To run it locally, you can use the following command:&lt;/p&gt;
  2936. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  2937. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  2938. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
  2939. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  2940. &lt;td class="lntd"&gt;
  2941. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;azurite&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  2942. &lt;/div&gt;
  2943. &lt;/div&gt;
  2944. &lt;blockquote class='info'&gt;
  2945. &lt;div class='mb-2'&gt;
  2946. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  2947. &lt;/div&gt;
  2948. &lt;p&gt;
  2949. &lt;span&gt;More information about Azurite can be found on the &lt;a href="https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=npm%2Cblob-storage"&gt;Use the Azurite emulator for local Azure Storage development&lt;/a&gt; documentation&lt;/span&gt;
  2950. &lt;/p&gt;
  2951. &lt;/blockquote&gt;</content:encoded></item><item><title>Help my browser keeps refreshing my SharePoint page</title><link>https://www.eliostruyf.com/browser-refreshing-sharepoint-page/</link><pubDate>Fri, 23 Feb 2024 14:05:23 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/browser-refreshing-sharepoint-page/</guid><description>One of my customers reported that their SharePoint page kept refreshing in Firefox. While investigating the issue, it turned out it was an issue with the Microsoft Graph permission scope that was missing.
  2952. All we had to do was approve the permission scope in the SharePoint Admin Center - API access page, and it was fixed; the page stopped refreshing.</description><content:encoded>&lt;p&gt;One of my customers reported that their SharePoint page kept refreshing in Firefox. While investigating the issue, it turned out it was an issue with the Microsoft Graph permission scope that was missing.&lt;/p&gt;
  2953. &lt;p&gt;All we had to do was approve the permission scope in the SharePoint Admin Center - API access page, and it was fixed; the page stopped refreshing.&lt;/p&gt;
  2954. &lt;p&gt;Although the solution is simple, I wanted to understand why this was happening.&lt;/p&gt;
  2955. &lt;h2 id="why-does-the-page-keep-refreshing"&gt;Why does the page keep refreshing?&lt;/h2&gt;
  2956. &lt;p&gt;Let us first check the experience:&lt;/p&gt;
  2957. &lt;div class="caption my-4"&gt;
  2958. &lt;figure class="caption__figure"&gt;
  2959. &lt;video width="100%" controls&gt;
  2960. &lt;source src="https://www.eliostruyf.com/uploads/2024/02/page-refresh-issue.mov" type="video/mp4"&gt;
  2961. &lt;/video&gt;
  2962. &lt;figcaption class="caption__text"&gt;SharePoint page refresh issue&lt;/figcaption&gt;
  2963. &lt;/figure&gt;
  2964. &lt;/div&gt;
  2965. &lt;p&gt;When you look closely at the video, you will see that the page gets redirected to &lt;code&gt;/_forms/spfxsinglesignon.aspx&lt;/code&gt;, bringing you back to the original page.&lt;/p&gt;
  2966. &lt;p&gt;The &lt;code&gt;/_forms/spfxsinglesignon.aspx&lt;/code&gt; page is used to overcome an issue with the third-party cookies, which are blocked by default in Firefox, and soon other browsers will do the same.&lt;/p&gt;
  2967. &lt;blockquote class='info'&gt;
  2968. &lt;div class='mb-2'&gt;
  2969. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  2970. &lt;/div&gt;
  2971. &lt;p&gt;
  2972. &lt;span&gt;The Chrome team is currently rolling this out (Q3 2024) - &lt;a href="https://developers.google.com/privacy-sandbox/3pcd"&gt;Prepare for third-party cookie restrictions&lt;/a&gt;&lt;/span&gt;
  2973. &lt;/p&gt;
  2974. &lt;/blockquote&gt;
  2975. &lt;p&gt;As Firefox is blocking the third-party cookies, the flow of loading your page and solutions goes like this:&lt;/p&gt;
  2976. &lt;ul&gt;
  2977. &lt;li&gt;The page loads&lt;/li&gt;
  2978. &lt;li&gt;The solution loads&lt;/li&gt;
  2979. &lt;li&gt;The solution code tries to retrieve an access token for Microsoft Graph&lt;/li&gt;
  2980. &lt;li&gt;The page gets redirected to &lt;code&gt;/_forms/spfxsinglesignon.aspx&lt;/code&gt; to get the access token&lt;/li&gt;
  2981. &lt;li&gt;An error is returned (but you do not see this), and the page gets redirected back to the original page&lt;/li&gt;
  2982. &lt;/ul&gt;
  2983. &lt;div class="caption my-4"&gt;
  2984. &lt;figure class="caption__figure"&gt;
  2985. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/msal-error.png" title="Show image"&gt;
  2986. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  2987. &lt;img data-lqip="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAABCAYAAADn9T9&amp;#43;AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAJElEQVR4nGPISIz/nxof&amp;#43;x9Mx8X8T4FikBgIp8RG/09LjP8PAKwBFUdUJJ/5AAAAAElFTkSuQmCC" src="https://www.eliostruyf.com/uploads/2024/02/msal-error.png" alt="Error during the token retrieval" style="width: 1224px;" class="lazyload" /&gt;
  2988. &lt;/a&gt;
  2989. &lt;figcaption class="caption__text"&gt;Error during the token retrieval&lt;/figcaption&gt;
  2990. &lt;/figure&gt;
  2991. &lt;/div&gt;
  2992. &lt;ul&gt;
  2993. &lt;li&gt;The original page loads and it starts all over again&lt;/li&gt;
  2994. &lt;/ul&gt;
  2995. &lt;h2 id="why-did-it-not-happen-before"&gt;Why did it not happen before?&lt;/h2&gt;
  2996. &lt;p&gt;The issue does not occur in Chrome or Edge when writing the article. Third-party cookies are not yet blocked by default in these browsers.&lt;/p&gt;
  2997. &lt;p&gt;As it is not yet blocked, the access token for Microsoft Graph is retrieved with the implicit grant flow, which uses the hidden iframe. As this is not blocked, only an error is returned in the console.&lt;/p&gt;
  2998. &lt;div class="caption my-4"&gt;
  2999. &lt;figure class="caption__figure"&gt;
  3000. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/msal-error-console.webp" title="Show image"&gt;
  3001. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  3002. &lt;img data-lqip="data:image/jpeg;base64,UklGRl4AAABXRUJQVlA4WAoAAAAQAAAACQAAAQAAQUxQSA0AAAABD/B294gIsQIR/Q8FAFZQOCAqAAAA0AEAnQEqCgACAAFAJiWUAnQBA310CsAA/vfSRMvtBdvZcg2puVeryuAA" src="https://www.eliostruyf.com/uploads/2024/02/msal-error-console.webp" alt="MSAL token retrieval error" style="width: 2568px;" class="lazyload" /&gt;
  3003. &lt;/a&gt;
  3004. &lt;figcaption class="caption__text"&gt;MSAL token retrieval error&lt;/figcaption&gt;
  3005. &lt;/figure&gt;
  3006. &lt;/div&gt;
  3007. &lt;p&gt;This error gives the developer enough information to understand what is happening and fix the issue.&lt;/p&gt;
  3008. &lt;h2 id="how-to-fix-the-issue"&gt;How to fix the issue&lt;/h2&gt;
  3009. &lt;h3 id="solution-1-approve-the-permission-scope"&gt;Solution 1: Approve the permission scope&lt;/h3&gt;
  3010. &lt;p&gt;The clean solution is to approve the permission scope in the SharePoint Admin Center - API access page.&lt;/p&gt;
  3011. &lt;p&gt;This solution allows your code to retrieve the access token by just one redirect and stops the page from refreshing.&lt;/p&gt;
  3012. &lt;h3 id="solution-2-configure-sites-for-third-party-cookie-exceptions"&gt;Solution 2: Configure sites for third-party cookie exceptions&lt;/h3&gt;
  3013. &lt;p&gt;It feels like we are back in Internet Explorer, where we had to tell our customers to add their SharePoint URL to the trusted sites. For Firefox, you can configure the third-party cookie exceptions in the settings.&lt;/p&gt;
  3014. &lt;p&gt;Follow the following steps to configure the exceptions:&lt;/p&gt;
  3015. &lt;ul&gt;
  3016. &lt;li&gt;Open the Firefox settings&lt;/li&gt;
  3017. &lt;li&gt;Go to the &lt;code&gt;Privacy &amp;amp; Security&lt;/code&gt; tab&lt;/li&gt;
  3018. &lt;li&gt;In the &lt;code&gt;Enhanced Tracking Protection&lt;/code&gt; section, click on the &lt;code&gt;Manage Exceptions...&lt;/code&gt; button&lt;/li&gt;
  3019. &lt;li&gt;Add the SharePoint URL to the exceptions&lt;/li&gt;
  3020. &lt;/ul&gt;
  3021. &lt;div class="caption my-4"&gt;
  3022. &lt;figure class="caption__figure"&gt;
  3023. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/firefox-exceptions.png" title="Show image"&gt;
  3024. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  3025. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAFCAYAAAB8ZH1oAAAAAklEQVR4AewaftIAAACZSURBVDXBzWrCQBSA0W9u7oiLYvEHFN0VobQb3/89XIhrF4agCZJpmsxMxgTxHLP/&amp;#43;j6pZotMhMn0A&amp;#43;dqmqbhJSGSoap3LatyGUJY/fwe2Ky3WKuM6tpRFDnn85GB0QgkEf69J68qYoyMRIQ2Bt5UjSECPgTkUZJSous6Rt53vGnf9zdjTBJVZp9zrLXUf452s8MVOVwvDO5Pvx9DV13RrgMAAAAASUVORK5CYII=" src="https://www.eliostruyf.com/uploads/2024/02/firefox-exceptions.png" alt="Firefox third-party cookie exceptions" style="width: 2602px;" class="lazyload" /&gt;
  3026. &lt;/a&gt;
  3027. &lt;figcaption class="caption__text"&gt;Firefox third-party cookie exceptions&lt;/figcaption&gt;
  3028. &lt;/figure&gt;
  3029. &lt;/div&gt;
  3030. &lt;p&gt;Once configured, the solution will now try to retrieve the access token with the hidden iframe instead of getting redirected to &lt;code&gt;/_forms/spfxsinglesignon.aspx&lt;/code&gt;.&lt;/p&gt;
  3031. &lt;blockquote class='important'&gt;
  3032. &lt;div class='mb-2'&gt;
  3033. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  3034. &lt;/div&gt;
  3035. &lt;p&gt;
  3036. &lt;span&gt;Chrome and Edge have similar settings.&lt;/span&gt;
  3037. &lt;/p&gt;
  3038. &lt;/blockquote&gt;
  3039. &lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
  3040. &lt;p&gt;If you want to know more about the third-party cookie restrictions, you can read the following resources:&lt;/p&gt;
  3041. &lt;ul&gt;
  3042. &lt;li&gt;&lt;a href="https://developers.google.com/privacy-sandbox/3pcd"&gt;Prepare for third-party cookie restrictions&lt;/a&gt;&lt;/li&gt;
  3043. &lt;li&gt;&lt;a href="https://www.blimped.nl/spfx-authentication-and-third-party-cookies/"&gt;SharePoint Framework (SPFx), Authenticating to API&amp;rsquo;s and Third Party Cookies&lt;/a&gt;&lt;/li&gt;
  3044. &lt;li&gt;&lt;a href="https://desystemshelp.leeds.ac.uk/help-allowing-third-party-cookies/"&gt;Allowing third-party cookies on your device&lt;/a&gt;&lt;/li&gt;
  3045. &lt;li&gt;&lt;a href="https://github.com/SharePoint/sp-dev-docs/issues/5966"&gt;Plans for MSAL 2.0 / Third-party cookies #5966&lt;/a&gt;&lt;/li&gt;
  3046. &lt;/ul&gt;</content:encoded></item><item><title>Protect keys by keeping those out of your VS Code settings</title><link>https://www.eliostruyf.com/protect-api-auth-keys-keeping-out-vscode-settings/</link><pubDate>Wed, 21 Feb 2024 12:31:41 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/protect-api-auth-keys-keeping-out-vscode-settings/</guid><description>While implementing the i18n (internationalization) features in Front Matter CMS, I wanted to include the ability for users to use DeepL to translate their content automatically.
  3047. To be able to use DeepL, you need to have an authentication key, and the user will provide this authentication key.
  3048. Initially, I defined it as a configurable setting in the extension.</description><content:encoded>&lt;p&gt;While implementing the i18n (internationalization) features in &lt;a href="https://frontmatter.codes"&gt;Front Matter CMS&lt;/a&gt;, I wanted to include the ability for users to use DeepL to translate their content automatically.&lt;/p&gt;
  3049. &lt;p&gt;To be able to use DeepL, you need to have an authentication key, and the user will provide this authentication key.&lt;/p&gt;
  3050. &lt;p&gt;Initially, I defined it as a configurable setting in the extension. It is the most straightforward way to do it, but then I realized that keeping the key in the extension settings would be a bad idea as it might lead to a security risk.&lt;/p&gt;
  3051. &lt;p&gt;In this post, I will explain why it is a bad idea and how you can protect your keys by keeping those out of your Visual Studio Code settings.&lt;/p&gt;
  3052. &lt;p&gt;The article is intended for developers and extension users.&lt;/p&gt;
  3053. &lt;ul&gt;
  3054. &lt;li&gt;For developers, you will understand why it is a bad idea to keep keys in the settings of the extension and how to protect those keys for your users better.&lt;/li&gt;
  3055. &lt;li&gt;For extension users, you will understand why you should be careful when providing your API/Authentication keys to an extension and understand how they might be stored.&lt;/li&gt;
  3056. &lt;/ul&gt;
  3057. &lt;h2 id="why-it-is-a-bad-idea-to-keep-keys-in-the-settings-of-the-extension"&gt;Why it is a bad idea to keep keys in the settings of the extension&lt;/h2&gt;
  3058. &lt;p&gt;The settings of Visual Studio Code and the installed extensions are stored in JSON files. There are two levels of settings:&lt;/p&gt;
  3059. &lt;ol&gt;
  3060. &lt;li&gt;&lt;strong&gt;User Settings&lt;/strong&gt;: These settings are applied globally to any instance of VS Code.&lt;/li&gt;
  3061. &lt;li&gt;&lt;strong&gt;Workspace Settings&lt;/strong&gt;: These settings are applied to the current workspace/project.&lt;/li&gt;
  3062. &lt;/ol&gt;
  3063. &lt;p&gt;As mentioned, those settings are stored in a &lt;code&gt;settings.json&lt;/code&gt; file, and its location depends on the type of setting level you are configuring.&lt;/p&gt;
  3064. &lt;p&gt;The user settings its &lt;code&gt;settings.json&lt;/code&gt; file is stored outside of the current workspace/project into a location which is specific to the operation system the user uses.&lt;/p&gt;
  3065. &lt;blockquote class='info'&gt;
  3066. &lt;div class='mb-2'&gt;
  3067. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  3068. &lt;/div&gt;
  3069. &lt;p&gt;
  3070. &lt;span&gt;We can consider that the &lt;strong&gt;user settings&lt;/strong&gt; are safe as long the user doesn&amp;rsquo;t share the file or a malicious extension access it.&lt;/span&gt;
  3071. &lt;/p&gt;
  3072. &lt;/blockquote&gt;
  3073. &lt;p&gt;The workspace settings are stored in a &lt;code&gt;.vscode/settings.json&lt;/code&gt; file in the root of the workspace/project, and this is where a security issue could arise.&lt;/p&gt;
  3074. &lt;p&gt;If you store your keys in the workspace settings and commit those settings to a repository, you are exposing your keys to anyone with access to the repository.&lt;/p&gt;
  3075. &lt;h3 id="example-of-an-extension"&gt;Example of an extension&lt;/h3&gt;
  3076. &lt;blockquote class='important'&gt;
  3077. &lt;div class='mb-2'&gt;
  3078. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  3079. &lt;/div&gt;
  3080. &lt;p&gt;
  3081. &lt;span&gt;To illustrate the issue, I will use an example of an extension that uses an API key. I won&amp;rsquo;t mention the extension&amp;rsquo;s name, but I will show you how the settings are stored.&lt;/span&gt;
  3082. &lt;/p&gt;
  3083. &lt;/blockquote&gt;
  3084. &lt;p&gt;On the Visual Studio Code marketplace, I searched for an extension that uses OpenAI, as this is a typical example of an extension requiring an API key.&lt;/p&gt;
  3085. &lt;p&gt;In just seconds, I found one which used a setting named &lt;code&gt;***.apiKey&lt;/code&gt;.&lt;/p&gt;
  3086. &lt;p&gt;The next step is to discover if users accidentally expose their keys. All I need to do is to search for the &lt;code&gt;***.apiKey&lt;/code&gt; in the GitHub search.&lt;/p&gt;
  3087. &lt;div class="caption my-4"&gt;
  3088. &lt;figure class="caption__figure"&gt;
  3089. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/exposed-api-keys.webp" title="Show image"&gt;
  3090. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  3091. &lt;img data-lqip="data:image/jpeg;base64,UklGRnIAAABXRUJQVlA4WAoAAAAQAAAACQAABQAAQUxQSCMAAAABHyAQSPydVpgpjYgIGYratoGqvroLXlX&amp;#43;ZIYhov8RH6EnPgBWUDggKAAAAHABAJ0BKgoABgABQCYlnALsAYhAAP78nNQFqT9Untiorle/sZNUAAA=" src="https://www.eliostruyf.com/uploads/2024/02/exposed-api-keys.webp" alt="Exposed API Keys" style="width: 1482px;" class="lazyload" /&gt;
  3092. &lt;/a&gt;
  3093. &lt;figcaption class="caption__text"&gt;Exposed API Keys&lt;/figcaption&gt;
  3094. &lt;/figure&gt;
  3095. &lt;/div&gt;
  3096. &lt;p&gt;There are many results; I didn&amp;rsquo;t have to search for a long time to find them.&lt;/p&gt;
  3097. &lt;h3 id="what-can-happen-if-your-keys-are-exposed"&gt;What can happen if your keys are exposed?&lt;/h3&gt;
  3098. &lt;p&gt;If your keys are exposed, it can lead to a lot of problems:&lt;/p&gt;
  3099. &lt;ul&gt;
  3100. &lt;li&gt;&lt;strong&gt;Financial loss&lt;/strong&gt;: If you are using a paid service, someone could use your key to make requests, and you will be charged for those requests.&lt;/li&gt;
  3101. &lt;li&gt;&lt;strong&gt;Data loss&lt;/strong&gt;: If you are using a service that allows you to store data, someone could delete your data.&lt;/li&gt;
  3102. &lt;li&gt;&lt;strong&gt;Reputation loss&lt;/strong&gt;: If you are using a service that allows you to send emails, someone could send spam emails using your key, and your reputation will be affected.&lt;/li&gt;
  3103. &lt;/ul&gt;
  3104. &lt;h2 id="how-to-protect-your-keys"&gt;How to protect your keys&lt;/h2&gt;
  3105. &lt;h3 id="as-an-extension-developer"&gt;As an extension developer&lt;/h3&gt;
  3106. &lt;p&gt;If you are an extension developer, you should avoid storing that key in the Visual Studio Code settings.&lt;/p&gt;
  3107. &lt;p&gt;Instead, using the &lt;strong&gt;SecretStorage API&lt;/strong&gt; provided by Visual Studio Code is better. This API is cross-platform, and its intention is to store secret or sensitive data.&lt;/p&gt;
  3108. &lt;blockquote class='info'&gt;
  3109. &lt;div class='mb-2'&gt;
  3110. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  3111. &lt;/div&gt;
  3112. &lt;p&gt;
  3113. &lt;span&gt;I already wrote about this API in a previous post: &lt;a href="https://www.eliostruyf.com/devhack-code-extension-storage-options/"&gt;#DevHack: VS Code extension storage options&lt;/a&gt;.&lt;/span&gt;
  3114. &lt;/p&gt;
  3115. &lt;/blockquote&gt;
  3116. &lt;p&gt;The downside of using the SecretStorage API is that you will need to tell your user to provide the key via the input box or within a custom UI.&lt;/p&gt;
  3117. &lt;p&gt;For example:&lt;/p&gt;
  3118. &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
  3119. &lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
  3120. &lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
  3121. &lt;/span&gt;&lt;span class="lnt"&gt; 2
  3122. &lt;/span&gt;&lt;span class="lnt"&gt; 3
  3123. &lt;/span&gt;&lt;span class="lnt"&gt; 4
  3124. &lt;/span&gt;&lt;span class="lnt"&gt; 5
  3125. &lt;/span&gt;&lt;span class="lnt"&gt; 6
  3126. &lt;/span&gt;&lt;span class="lnt"&gt; 7
  3127. &lt;/span&gt;&lt;span class="lnt"&gt; 8
  3128. &lt;/span&gt;&lt;span class="lnt"&gt; 9
  3129. &lt;/span&gt;&lt;span class="lnt"&gt;10
  3130. &lt;/span&gt;&lt;span class="lnt"&gt;11
  3131. &lt;/span&gt;&lt;span class="lnt"&gt;12
  3132. &lt;/span&gt;&lt;span class="lnt"&gt;13
  3133. &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
  3134. &lt;td class="lntd"&gt;
  3135. &lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// On loading your extension, you can retrieve the API key from the secrets storage
  3136. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`deepl.apiKey`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  3137. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
  3138. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  3139. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// If the key is not found, you can ask the user to provide it
  3140. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showInputBox&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  3141. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;placeHolder&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`Enter your DeepL API key`&lt;/span&gt;
  3142. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  3143. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  3144. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`deepl.apiKey`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  3145. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  3146. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  3147. &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
  3148. &lt;/div&gt;
  3149. &lt;/div&gt;
  3150. &lt;h3 id="as-an-extension-user"&gt;As an extension user&lt;/h3&gt;
  3151. &lt;p&gt;As an extension user, you should be careful when providing an extension&amp;rsquo;s API/Authentication keys. If you notice that the extension asks for those keys in the settings, you should know that those keys could be exposed. You can also make the extension developer aware of the issue.&lt;/p&gt;
  3152. &lt;blockquote class='important'&gt;
  3153. &lt;div class='mb-2'&gt;
  3154. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;important&lt;/span&gt;
  3155. &lt;/div&gt;
  3156. &lt;p&gt;
  3157. &lt;span&gt;If you are using an extension that requires an API key, you should check the documentation of the extension to see how the keys are stored.&lt;/span&gt;
  3158. &lt;/p&gt;
  3159. &lt;/blockquote&gt;</content:encoded></item><item><title>Using GitHub Project webhooks to manage labeling repo issues</title><link>https://www.eliostruyf.com/github-project-webhooks-manage-labeling-issues/</link><pubDate>Mon, 12 Feb 2024 08:40:39 +0000</pubDate><author>Elio Struyf</author><dc:creator>Elio Struyf</dc:creator><guid>https://www.eliostruyf.com/github-project-webhooks-manage-labeling-issues/</guid><description>For managing the Front Matter CMS releases, I have been using GitHub Projects in combination with GitHub Actions for automatically labeling my issues, which I add to the Project board. I must say: &amp;ldquo;Add to my Project (classic) board.&amp;rdquo;
  3160. The classic Projects experience has been discontinued, and there is a big difference when using the new Projects experience, which is creating projects on the user or organization level.</description><content:encoded>&lt;p&gt;For managing the Front Matter CMS releases, I have been using &lt;a href="https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects"&gt;GitHub Projects&lt;/a&gt; in combination with GitHub Actions for automatically labeling my issues, which I add to the Project board. I must say: &amp;ldquo;Add to my Project (classic) board.&amp;rdquo;&lt;/p&gt;
  3161. &lt;p&gt;The classic Projects experience has been discontinued, and there is a big difference when using the new Projects experience, which is creating projects on the user or organization level.&lt;/p&gt;
  3162. &lt;p&gt;Previously, you could create those classic projects on the repository level, which allowed hooking it up with some GitHub actions to add or remove issue labels when managing them on the project board. These labels enable quick filtering of issues in the list for a specific status or project.&lt;/p&gt;
  3163. &lt;div class="caption my-4"&gt;
  3164. &lt;figure class="caption__figure"&gt;
  3165. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/issue-project-labels.webp" title="Show image"&gt;
  3166. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  3167. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAADCAYAAACqPZ51AAAAAklEQVR4AewaftIAAABmSURBVE3BQQ6CMBBA0T&amp;#43;2kLAgJsiChTHR&amp;#43;x&amp;#43;Bu5hwAtuggTrTwaXvyTBd58sw3gRo2pb3ugICOKcQqGao6hLd/R677tEIaIXan6n5RRChlEJKiZ8Q7WvPbd/NRHCB/ClYzqDKn&amp;#43;UAG7EtmFsl3fYAAAAASUVORK5CYII=" src="https://www.eliostruyf.com/uploads/2024/02/issue-project-labels.webp" alt="Project labels on the issues" style="width: 900px;" class="lazyload" /&gt;
  3168. &lt;/a&gt;
  3169. &lt;figcaption class="caption__text"&gt;Project labels on the issues&lt;/figcaption&gt;
  3170. &lt;/figure&gt;
  3171. &lt;/div&gt;
  3172. &lt;blockquote class='info'&gt;
  3173. &lt;div class='mb-2'&gt;
  3174. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  3175. &lt;/div&gt;
  3176. &lt;p&gt;
  3177. &lt;span&gt;Read more about on &lt;a href="https://www.eliostruyf.com/adding-or-deleting-github-project-labels-on-issues/"&gt;Adding or deleting GitHub project (classic) labels on issues&lt;/a&gt;.&lt;/span&gt;
  3178. &lt;/p&gt;
  3179. &lt;/blockquote&gt;
  3180. &lt;p&gt;When I wanted to move to the new project experience, I had to create my project on the user or organizational level, meaning I had to find another solution for automatically labeling my issues.&lt;/p&gt;
  3181. &lt;p&gt;The approach for automating your project management is by using webhooks. Webhooks allow your integrations to listen to event actions that occur on GitHub, so it can be used to notify you when a new project is created or when you manage the project items (create, edit, delete).&lt;/p&gt;
  3182. &lt;blockquote class='info'&gt;
  3183. &lt;div class='mb-2'&gt;
  3184. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  3185. &lt;/div&gt;
  3186. &lt;p&gt;
  3187. &lt;span&gt;Read more on &lt;a href="https://docs.github.com/en/webhooks"&gt;GitHub webhooks documentation&lt;/a&gt;.&lt;/span&gt;
  3188. &lt;/p&gt;
  3189. &lt;/blockquote&gt;
  3190. &lt;p&gt;In this article, I will guide you through how you can configure and use the GitHub webhook functionality for project management.&lt;/p&gt;
  3191. &lt;h2 id="limitation"&gt;Limitation&lt;/h2&gt;
  3192. &lt;p&gt;There is a limitation; the approach I will tell you more about is only possible on projects created at the organizational level. The reason is that when writing this article, GitHub does not yet allow you to create project webhooks on the user level.&lt;/p&gt;
  3193. &lt;h2 id="my-project-and-repository-setup"&gt;My project and repository setup&lt;/h2&gt;
  3194. &lt;p&gt;My setup is the following:&lt;/p&gt;
  3195. &lt;ul&gt;
  3196. &lt;li&gt;Repository is managed on user level&lt;/li&gt;
  3197. &lt;li&gt;Project is created on an organizational level&lt;/li&gt;
  3198. &lt;/ul&gt;
  3199. &lt;blockquote class='info'&gt;
  3200. &lt;div class='mb-2'&gt;
  3201. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  3202. &lt;/div&gt;
  3203. &lt;p&gt;
  3204. &lt;span&gt;I did not want to move the repository, so I kept it on the user level, but you can also be on an organization.&lt;/span&gt;
  3205. &lt;/p&gt;
  3206. &lt;/blockquote&gt;
  3207. &lt;h2 id="github-app"&gt;GitHub App&lt;/h2&gt;
  3208. &lt;p&gt;For the authentication and webhook configuration, I choose to use a GitHub App, as an app provides you with all the functionality you need for this kind of scenario.&lt;/p&gt;
  3209. &lt;p&gt;You can create a new app in the &lt;a href="https://github.com/settings/apps/new"&gt;developer settings - register new GitHub App&lt;/a&gt;.&lt;/p&gt;
  3210. &lt;p&gt;I configured the app as follows:&lt;/p&gt;
  3211. &lt;ul&gt;
  3212. &lt;li&gt;Activate the &lt;strong&gt;webhook&lt;/strong&gt; functionality, and set the URL to your endpoint. If you do not yet know it, you could use a service like &lt;a href="https://smee.io"&gt;smee.io&lt;/a&gt; to experiment with it.&lt;/li&gt;
  3213. &lt;li&gt;Set a webhook secret to make the webhook functionality more secure.&lt;/li&gt;
  3214. &lt;li&gt;Set the permissions
  3215. &lt;ul&gt;
  3216. &lt;li&gt;Repository permissions:
  3217. &lt;ul&gt;
  3218. &lt;li&gt;&lt;strong&gt;Issues&lt;/strong&gt;: Read &amp;amp; write&lt;/li&gt;
  3219. &lt;li&gt;&lt;strong&gt;Metadata&lt;/strong&gt;: Read-only&lt;/li&gt;
  3220. &lt;/ul&gt;
  3221. &lt;/li&gt;
  3222. &lt;li&gt;Organization permissions:
  3223. &lt;ul&gt;
  3224. &lt;li&gt;&lt;strong&gt;Projects&lt;/strong&gt;: Read-only&lt;/li&gt;
  3225. &lt;/ul&gt;
  3226. &lt;/li&gt;
  3227. &lt;/ul&gt;
  3228. &lt;/li&gt;
  3229. &lt;li&gt;Subscribe to &lt;strong&gt;Project V2 item&lt;/strong&gt; events&lt;/li&gt;
  3230. &lt;li&gt;Create a &lt;strong&gt;private key&lt;/strong&gt;, which you need for authenticating your app.&lt;/li&gt;
  3231. &lt;/ul&gt;
  3232. &lt;h3 id="installing-the-app"&gt;Installing the app&lt;/h3&gt;
  3233. &lt;p&gt;Once you have created the app, you can install the app on the user/organization.&lt;/p&gt;
  3234. &lt;p&gt;If you use a setup similar to mine, you will notice the difference between the user and organization installation permissions.&lt;/p&gt;
  3235. &lt;div class="caption my-4"&gt;
  3236. &lt;figure class="caption__figure"&gt;
  3237. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/github-app-personal.webp" title="Show image"&gt;
  3238. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  3239. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAECAYAAAC3OK7NAAAAAklEQVR4AewaftIAAABhSURBVHXBOw7CQAxAweeNreQIyaVokDguPUG05Aik3Wo/IJuGCikzcjmdH7fnNvfWKO1NIBAQBPFp/Ox6Xe9LSmkREUYbqLXi7vwRHaeJISVyzrg7R7SU8jLVMDN67xzYv5tpKTiMXXVVAAAAAElFTkSuQmCC" src="https://www.eliostruyf.com/uploads/2024/02/github-app-personal.webp" alt="GitHub App installed on user level" style="width: 900px;" class="lazyload" /&gt;
  3240. &lt;/a&gt;
  3241. &lt;figcaption class="caption__text"&gt;GitHub App installed on user level&lt;/figcaption&gt;
  3242. &lt;/figure&gt;
  3243. &lt;/div&gt;
  3244. &lt;div class="caption my-4"&gt;
  3245. &lt;figure class="caption__figure"&gt;
  3246. &lt;a class="lightbox" href="https://www.eliostruyf.com/uploads/2024/02/github-app-organization.webp" title="Show image"&gt;
  3247. &lt;span class="sr-only"&gt;Show image&lt;/span&gt;
  3248. &lt;img data-lqip="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAECAYAAAC3OK7NAAAAAklEQVR4AewaftIAAABjSURBVG3Buw3CUAyG0c9c8/AGZCgaJNalB0QLI5AJsHKD/NNQoZxjp8Pxfnk893Pv5DQjDARC6DPxM/r5ehtaa4OZsd04mYmq&amp;#43;GOriGAXgSSqClWxxN&amp;#43;Zr7W7qoreO2bGgvELyCUvYltfLuYAAAAASUVORK5CYII=" src="https://www.eliostruyf.com/uploads/2024/02/github-app-organization.webp" alt="GitHub App installed on organization level" style="width: 900px;" class="lazyload" /&gt;
  3249. &lt;/a&gt;
  3250. &lt;figcaption class="caption__text"&gt;GitHub App installed on organization level&lt;/figcaption&gt;
  3251. &lt;/figure&gt;
  3252. &lt;/div&gt;
  3253. &lt;h2 id="the-sample-project"&gt;The sample project&lt;/h2&gt;
  3254. &lt;p&gt;I have created a sample project to get you started. You can find it here: &lt;a href="https://github.com/estruyf/github-project-labeling"&gt;GitHub Project Labeling&lt;/a&gt;.&lt;/p&gt;
  3255. &lt;blockquote class='info'&gt;
  3256. &lt;div class='mb-2'&gt;
  3257. &lt;span class="block__pill rounded-full text-white uppercase px-4 py-1 text-xs font-bold"&gt;info&lt;/span&gt;
  3258. &lt;/div&gt;
  3259. &lt;p&gt;
  3260. &lt;span&gt;The sample project will also work when you use it on an organizational level only.&lt;/span&gt;
  3261. &lt;/p&gt;
  3262. &lt;/blockquote&gt;
  3263. &lt;p&gt;The project makes use of Azure Functions, and if you deploy to Azure, all you need to do is configure the following settings:&lt;/p&gt;
  3264. &lt;ul&gt;
  3265. &lt;li&gt;&lt;strong&gt;WEBHOOK_SECRET&lt;/strong&gt;: set the value which you provided when creating the GitHub App&lt;/li&gt;
  3266. &lt;li&gt;&lt;strong&gt;GITHUB_APP_ID&lt;/strong&gt;: this can be found on the GitHub App instance once you have created it&lt;/li&gt;
  3267. &lt;li&gt;&lt;strong&gt;GITHUB_APP_PRIVATE_KEY&lt;/strong&gt;: add the private key. You can put it in Azure Key Vault and link it to your Azure Function.&lt;/li&gt;
  3268. &lt;/ul&gt;
  3269. &lt;h3 id="following-the-best-practices"&gt;Following the best practices&lt;/h3&gt;
  3270. &lt;p&gt;The Azure Function follows the &lt;a href="https://docs.github.com/en/webhooks/using-webhooks/best-practices-for-using-webhooks"&gt;best practices&lt;/a&gt;:&lt;/p&gt;
  3271. &lt;ul&gt;
  3272. &lt;li&gt;Subscribe to a minimum of events&lt;/li&gt;
  3273. &lt;li&gt;Use a webhook secret and validate it for each call&lt;/li&gt;
  3274. &lt;li&gt;Check the event type before processing&lt;/li&gt;
  3275. &lt;li&gt;Use the &lt;code&gt;X-GitHub-Delivery&lt;/code&gt; to prevent replay attacks&lt;/li&gt;
  3276. &lt;/ul&gt;
  3277. &lt;h3 id="the-logic"&gt;The logic&lt;/h3&gt;
  3278. &lt;p&gt;The logic for the app works as follows:&lt;/p&gt;
  3279. &lt;h4 id="on-project-item-creation-or-moving-between-statuses"&gt;On project item creation or moving between statuses&lt;/h4&gt;
  3280. &lt;ul&gt;
  3281. &lt;li&gt;GitHub triggers the Azure Function webhook&lt;/li&gt;
  3282. &lt;li&gt;The project item is retrieved and checked if it is an &lt;strong&gt;issue&lt;/strong&gt;; if not, it will not continue.&lt;/li&gt;
  3283. &lt;li&gt;The issue is retrieved&lt;/li&gt;
  3284. &lt;li&gt;The repository labels are retrieved&lt;/li&gt;
  3285. &lt;li&gt;Project label is created and set&lt;/li&gt;
  3286. &lt;li&gt;The status label is created and set&lt;/li&gt;
  3287. &lt;li&gt;Other status labels are removed from the issue&lt;/li&gt;
  3288. &lt;/ul&gt;
  3289. &lt;h4 id="on-project-item-removal"&gt;On project item removal&lt;/h4&gt;
  3290. &lt;ul&gt;
  3291. &lt;li&gt;Removal of the project label&lt;/li&gt;
  3292. &lt;li&gt;Removal of the status label&lt;/li&gt;
  3293. &lt;/ul&gt;</content:encoded></item></channel></rss>
Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda