[{"data":1,"prerenderedAt":12914},["Reactive",2],{"navigation":3,"aAII9Cz3yR":204,"tags-tooling":397},[4,192,200],{"title":5,"_path":6,"children":7,"icon":191},"Blog","/posts",[8,11,14,17,20,23,26,29,32,35,38,41,44,47,50,53,56,59,62,65,68,71,74,77,80,83,86,89,92,95,98,101,104,107,110,113,116,119,122,125,128,131,134,137,140,143,146,149,152,155,158,161,164,167,170,173,176,179,182,185,188],{"title":9,"_path":10},"Testing your API with REST Client","/posts/testing-your-api-with-rest-client",{"title":12,"_path":13},"HTML templating in Xamarin","/posts/html-templating-in-xamarin",{"title":15,"_path":16},"Goodbye Azure Portal, Welcome Azure CLI","/posts/welcome-azure-cli",{"title":18,"_path":19},"Coming across Gitpod","/posts/gitpod",{"title":21,"_path":22},"Handle token retrieval while querying an API","/posts/delegating-handler",{"title":24,"_path":25},"Clean up your local git branches.","/posts/cleaning-git-branches",{"title":27,"_path":28},"Automate configuration of Teams Tab SSO with PowerShell.","/posts/teams-sso-powershell",{"title":30,"_path":31},"How to do a technology watch? - Part 1","/posts/technology-watch-part1",{"title":33,"_path":34},"How to do a technology watch? - Part 2","/posts/technology-watch-part2",{"title":36,"_path":37},"You almost no longer need Key Vault references for Azure Functions.","/posts/azure-functions-custom-configuration",{"title":39,"_path":40},"How to do a technology watch? - Part 3","/posts/technology-watch-part3",{"title":42,"_path":43},"Forget DevOps, the future is already here!","/posts/devops-future",{"title":45,"_path":46},"Week 9, 2021 - Tips I learned this week","/posts/w09-2021-tips-learned-this-week",{"title":48,"_path":49},"Week 12, 2021 - Tips I learned this week","/posts/w12-2021-tips-learned-this-week",{"title":51,"_path":52},"Week 14, 2021 - Tips I learned this week","/posts/w14-2021-tips-learned-this-week",{"title":54,"_path":55},"Once upon a time in .NET","/posts/once-upon-a-time-in-dotnet",{"title":57,"_path":58},"Install your applications with winget","/posts/winget-import",{"title":60,"_path":61},"Customize your applications when installing them with winget","/posts/winget-override",{"title":63,"_path":64},"Week 22, 2021 - Tips I learned this week","/posts/w22-2021-tips-learned-this-week",{"title":66,"_path":67},"How to connect to an Azure SQL Database from C# using Azure AD","/posts/sqlclient-active-directory-authent",{"title":69,"_path":70},"Producing packages for Windows Package Manager","/posts/wingetcreate",{"title":72,"_path":73},"4 tips about GitHub Actions environment variables and contexts","/posts/github-actions-var-and-context",{"title":75,"_path":76},"AzureWebJobsStorage, the secret you don't need in your Function App.","/posts/azure-functions-without-azurewebjobsstorage",{"title":78,"_path":79},"ASP.NET Core - Lost in configuration","/posts/lost-in-configuration",{"title":81,"_path":82},"Week 39, 2021 - Tips I learned this week","/posts/w39-2021-tips-learned-this-week",{"title":84,"_path":85},"Week 41, 2021 - Tips I learned this week","/posts/w41-2021-tips-learned-this-week",{"title":87,"_path":88},"Migrating and open-sourcing my blog","/posts/migrating-blog",{"title":90,"_path":91},"Week 45, 2021 - Tips I learned this week","/posts/w45-2021-tips-learned-this-week",{"title":93,"_path":94},"Organize your GitHub stars with Astral","/posts/astral",{"title":96,"_path":97},"Pulumi with an Azure Blob Storage backend","/posts/pulumi-azure-backend",{"title":99,"_path":100},"IaC Hot Reload with Pulumi Watch","/posts/pulumi-watch",{"title":102,"_path":103},"Week 2, 2022 - Tips I learned this week","/posts/w02-2022-tips-learned-this-week",{"title":105,"_path":106},"Week 3, 2022 - Tips I learned this week","/posts/w03-2022-tips-learned-this-week",{"title":108,"_path":109},"Week 5, 2022 - Tips I learned this week","/posts/w05-2022-tips-learned-this-week",{"title":111,"_path":112},"How to provision an Azure SQL Database with Active Directory authentication","/posts/sqldatabase-active-directory-authent",{"title":114,"_path":115},"Why will I choose Pulumi over Terraform for my next project?","/posts/pulumi-vs-terraform",{"title":117,"_path":118},"Week 19, 2022 - Tips I learned this week","/posts/w19-2022-tips-learned-this-week",{"title":120,"_path":121},"Week 20, 2022 - Tips I learned this week","/posts/w20-2022-tips-learned-this-week",{"title":123,"_path":124},"Keeping secrets secure when using API Clients","/posts/http-clients-secrets",{"title":126,"_path":127},"What made me want to be a developer?","/posts/be-a-developer",{"title":129,"_path":130},"What can we do when stuck with a programming problem?","/posts/get-unstuck",{"title":132,"_path":133},"How did I automate the setup of my developer Windows laptop?","/posts/automate-developer-machine",{"title":135,"_path":136},"Discussion about API clients","/posts/http-clients",{"title":138,"_path":139},"Week 46, 2022 - Tips I learned this week","/posts/w46-2022-tips-learned-this-week",{"title":141,"_path":142},"When Pulumi met Nuke: a .NET love story","/posts/when-pulumi-met-nuke",{"title":144,"_path":145},"A year of learning and sharing - Dev Retro 2022","/posts/2022-retro",{"title":147,"_path":148},"Perform Dynamic Execution of an npm Package","/posts/pnpm-dlx",{"title":150,"_path":151},"Manage multiple Node.js versions","/posts/pnpm-env",{"title":153,"_path":154},"Introducing the Vue.js CI/CD series","/posts/vuecicd-introduction",{"title":156,"_path":157},"Execute commands using your project dependencies","/posts/pnpm-exec",{"title":159,"_path":160},"Vue.js CI/CD: Continuous Integration","/posts/vuecicd-ci",{"title":162,"_path":163},"Who is using pnpm?","/posts/pnpm-who-is-using",{"title":165,"_path":166},"Create an Azure-Ready GitHub Repository using Pulumi","/posts/azure-ready-github-repository",{"title":168,"_path":169},"Deploying to Azure from Azure DevOps without secrets","/posts/ado-workload-identity-federation",{"title":171,"_path":172},"Effortlessly Configure GitHub Repositories for Azure Deployment via OIDC","/posts/scripting-azure-ready-github-repository",{"title":174,"_path":175},"Playing with the .NET 8 Web API template","/posts/playing-with-dotnet8",{"title":177,"_path":178},"Another year of sharing and learning - Dev Retro 2023","/posts/2023-retro",{"title":180,"_path":181},"Week 4, 2024 - Tips I learned this week","/posts/w04-2024-tips-learned-this-week",{"title":183,"_path":184},"Using dependency injection with Azure .NET SDK","/posts/azure-sdk-di",{"title":186,"_path":187},"Having Fun With IT Event Calendars","/posts/it-event-calendars",{"title":189,"_path":190},"Call your Azure AD B2C protected API with authenticated HTTP requests from your JetBrains IDE","/posts/http-clients-oauth2","i-heroicons-newspaper",{"title":193,"_path":194,"children":195,"icon":199},"Goodies","/goodies",[196],{"title":197,"_path":198},"My Git Cheat Sheet","/goodies/gitcheatsheet","i-heroicons-gift-solid",{"title":201,"_path":202,"icon":203},"About","/about","i-heroicons-user-circle-solid",[205,207,209,211,214,217,220,223,226,229,231,234,237,240,242,244,247,250,253,255,258,261,264,267,270,273,276,279,282,285,287,289,292,294,297,300,303,305,308,310,313,316,319,322,325,327,329,332,335,338,341,344,347,350,353,356,359,361,363,366,369,372,375,377,380,383,385,388,391,394],[206,206],"tooling",[208,208],"vscode",[210,210],"rest",[212,213],"http","HTTP",[215,216],"razor","Razor",[218,219],"xamarin","Xamarin",[221,222],"templating","Templating",[224,225],"azure-cli","Azure CLI",[227,228],"azure","Azure",[230,230],"shell",[232,233],"github","GitHub",[235,236],"asp-net-core","ASP.NET Core",[238,239],"net",".NET",[241,241],"git",[243,243],"nushell",[245,246],"microsoft-teams","Microsoft Teams",[248,249],"powershell","PowerShell",[251,252],"azure-active-directory","Azure Active Directory",[254,254],"learning",[256,257],"azure-functions","Azure Functions",[259,260],"azure-key-vault","Azure Key Vault",[262,263],"configuration","Configuration",[265,266],"devops","DevOps",[268,269],"it","IT",[271,272],"tips-learned-this-week","tips learned this week",[274,275],"windows-terminal","Windows Terminal",[277,278],"azure-pipelines","Azure Pipelines",[280,281],"application-insights","Application Insights",[283,284],"azure-iot","Azure IoT",[286,286],"records",[288,288],"refit",[290,291],"development-box-setup","development box setup",[293,293],"winget",[295,296],"package-manager","package manager",[298,299],"azure-sql-database","Azure SQL Database",[301,302],"azure-sdk","Azure SDK",[304,304],"wingetcreate",[306,307],"github-actions","GitHub Actions",[309,309],"jq",[311,312],"pulumi","Pulumi",[314,315],"iac","IaC",[317,318],"azure-storage","Azure Storage",[320,321],"azure-signalr","Azure SignalR",[323,324],"visio","Visio",[326,326],"csharp",[328,328],"jest",[330,331],"statiq","Statiq",[333,334],"open-source","open source",[336,337],"visual-studio","Visual Studio",[339,340],"vue-js","Vue.js",[342,343],"azure-devops","Azure DevOps",[345,346],"vite","Vite",[348,349],"code-analysis","Code analysis",[351,352],"diagram","Diagram",[354,355],"terraform","Terraform",[357,358],"typescript","TypeScript",[360,360],"thoughts",[362,362],"pnpm",[364,365],"nuke","Nuke",[367,368],"pipelines","Pipelines",[370,371],"cicd","CI/CD",[373,374],"openid-connect","OpenID Connect",[376,376],"security",[378,379],"github-cli","GitHub CLI",[381,382],"microsoft-entra-id","Microsoft Entra ID",[384,384],"advent",[386,387],"finops","FinOps",[389,390],"anglesharp","AngleSharp",[392,393],"oauth2","OAuth2",[395,396],"azure-ad-b2c","Azure AD B2C",[398,1440,1655,1834,2010,2204,2458,3112,4637,5645,6825,7044,7348,7782,8024,8339,9670,9897,10982,11194,11771,12040],{"_path":190,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":189,"description":402,"lead":403,"date":404,"image":405,"badge":407,"tags":409,"ImageAttribution":410,"body":411,"_type":1435,"_id":1436,"_source":1437,"_file":1438,"_extension":1439},"posts",false,"","I have written several blog posts about HTTP clients in the past. I am a big fan of using HTTP text files versioned in a git repository alongside API code and executed by an IDE tooling. However, there was one use case where a GUI tool like Postman or a swagger page was more convenient: retrieving OAuth 2.0 users' tokens. Thanks to the latest OAuth 2.0 feature in JetBrains' IDE built-in HTTP client, this is no longer an issue.","Automatically acquire OAuth 2.0 tokens","2024-03-11T00:00:00.000Z",{"src":406},"/images/access-code-door.webp",{"label":408},"Tooling",[206,213,393,396],"Picture of \u003Ca href=\"https://unsplash.com/fr/@drice22\">Danielle Rice\u003C/a> on \u003Ca href=\"https://unsplash.com/fr/photos/personne-detenant-une-carte-en-noir-et-blanc-7GfRwb78YWs\">Unsplash\u003C/a>",{"type":412,"children":413,"toc":1425},"root",[414,442,449,471,476,481,487,492,521,526,539,558,563,576,582,587,615,622,636,650,655,660,685,690,726,743,756,762,767,772,1273,1281,1289,1302,1308,1313,1318,1330,1343,1383,1388,1394,1399,1419],{"type":415,"tag":416,"props":417,"children":418},"element","p",{},[419,422,431,433,440],{"type":420,"value":421},"text","I have written several ",{"type":415,"tag":423,"props":424,"children":428},"a",{"href":425,"rel":426},"https://www.techwatching.dev/posts/http-clients",[427],"nofollow",[429],{"type":420,"value":430},"blog posts",{"type":420,"value":432}," about HTTP clients in the past. I am a big fan of using HTTP text files versioned in a git repository alongside API code and executed by an IDE tooling. However, there was one use case where a GUI tool like Postman or a swagger page was more convenient: retrieving OAuth 2.0 users' tokens. Thanks to the latest ",{"type":415,"tag":423,"props":434,"children":437},{"href":435,"rel":436},"https://www.jetbrains.com/help/idea/oauth-2-0-authorization.html",[427],[438],{"type":420,"value":439},"OAuth 2.0 feature",{"type":420,"value":441}," in JetBrains' IDE built-in HTTP client, this is no longer an issue.",{"type":415,"tag":443,"props":444,"children":446},"h2",{"id":445},"context",[447],{"type":420,"value":448},"Context",{"type":415,"tag":416,"props":450,"children":451},{},[452,454,460,462,469],{"type":420,"value":453},"I am developing a web application composed of a Vue.js frontend and an ASP.NET Core backend (just describing my use case, technologies don't matter). The end users of this application are authenticated using ",{"type":415,"tag":423,"props":455,"children":458},{"href":456,"rel":457},"https://learn.microsoft.com/en-us/azure/active-directory-b2c/overview",[427],[459],{"type":420,"value":396},{"type":420,"value":461},", which is a ",{"type":415,"tag":423,"props":463,"children":466},{"href":464,"rel":465},"https://en.wikipedia.org/wiki/Customer_identity_access_management",[427],[467],{"type":420,"value":468},"customer identity access management",{"type":420,"value":470}," solution like Auth0 or other competitors.",{"type":415,"tag":416,"props":472,"children":473},{},[474],{"type":420,"value":475},"I often need to manually call the endpoints of the API to verify the code is working properly and that an endpoint is returning the expected result. HTTP files are a convenient way of writing and executing the HTTP requests. Once committed in the Git repository, they can easily be shared with other developers of the team who may not have worked on some endpoints and want to have proper examples with the query parameters and payloads.",{"type":415,"tag":416,"props":477,"children":478},{},[479],{"type":420,"value":480},"As the API is protected by Azure AD B2C, I need to retrieve a valid access token and pass it to my requests.",{"type":415,"tag":443,"props":482,"children":484},{"id":483},"previous-solutions",[485],{"type":420,"value":486},"Previous solutions",{"type":415,"tag":416,"props":488,"children":489},{},[490],{"type":420,"value":491},"Passing a valid access token to my HTTP requests is something I was previously doing by:",{"type":415,"tag":493,"props":494,"children":495},"ul",{},[496,502,507],{"type":415,"tag":497,"props":498,"children":499},"li",{},[500],{"type":420,"value":501},"signing in my frontend",{"type":415,"tag":497,"props":503,"children":504},{},[505],{"type":420,"value":506},"grabbing the token in the web browser dev tools",{"type":415,"tag":497,"props":508,"children":509},{},[510,512,519],{"type":420,"value":511},"copying the token to my ",{"type":415,"tag":423,"props":513,"children":516},{"href":514,"rel":515},"https://www.jetbrains.com/help/idea/exploring-http-syntax.html#environment-variables",[427],[517],{"type":420,"value":518},"HTTP environment variables",{"type":420,"value":520}," (preferably the private environment file to avoid committing a secret in your repository)",{"type":415,"tag":416,"props":522,"children":523},{},[524],{"type":420,"value":525},"That works but:",{"type":415,"tag":493,"props":527,"children":528},{},[529,534],{"type":415,"tag":497,"props":530,"children":531},{},[532],{"type":420,"value":533},"it's cumbersome",{"type":415,"tag":497,"props":535,"children":536},{},[537],{"type":420,"value":538},"you have to do it each time your access token expires",{"type":415,"tag":416,"props":540,"children":541},{},[542,544,556],{"type":420,"value":543},"Another solution is to use a tool that generates app-specific local JWTs and configure your local dev environment to authenticate with these tokens instead of using the Azure AD B2C configuration. In .NET, you can use the ",{"type":415,"tag":423,"props":545,"children":548},{"href":546,"rel":547},"https://learn.microsoft.com/en-us/aspnet/core/security/authentication/jwt-authn",[427],[549],{"type":415,"tag":550,"props":551,"children":553},"code",{"className":552},[],[554],{"type":420,"value":555},"dotnet user-jwts",{"type":420,"value":557}," to do exactly that. It allows you to generate a JWT token with the scopes, roles, and claims you want. So it's a good solution to debug your API locally without having to bypass the authentication and authorization mechanisms.",{"type":415,"tag":416,"props":559,"children":560},{},[561],{"type":420,"value":562},"However, it has some downsides:",{"type":415,"tag":493,"props":564,"children":565},{},[566,571],{"type":415,"tag":497,"props":567,"children":568},{},[569],{"type":420,"value":570},"the tokens are only valid in your local machine so it only works for your local environment",{"type":415,"tag":497,"props":572,"children":573},{},[574],{"type":420,"value":575},"the Azure AD B2C authentication is replaced by this \"local JWT authentication\" so you are not testing your API in real conditions",{"type":415,"tag":443,"props":577,"children":579},{"id":578},"with-the-new-http-client-oauth-20-feature",[580],{"type":420,"value":581},"With the new HTTP Client OAuth 2.0 feature",{"type":415,"tag":416,"props":583,"children":584},{},[585],{"type":420,"value":586},"Starting version 2024.1, HTTP Client in the JetBrains IDEs (in my case Rider 2024.1) support automatically authenticating HTTP requests, provided that you properly configured it.",{"type":415,"tag":588,"props":589,"children":591},"callout",{"icon":590},"i-heroicons-chat-bubble-left-20-solid",[592],{"type":415,"tag":416,"props":593,"children":594},{},[595,597,604,606,613],{"type":420,"value":596},"Support for OAuth 2.0 started in ",{"type":415,"tag":423,"props":598,"children":601},{"href":599,"rel":600},"https://blog.jetbrains.com/idea/2023/10/intellij-idea-2023-3-eap-3/#oauth-2.0-support",[427],[602],{"type":420,"value":603},"version 2023.3",{"type":420,"value":605},", however, Authorization Code Flow with PKCE (PKCE challenge is required in the ",{"type":415,"tag":423,"props":607,"children":610},{"href":608,"rel":609},"https://oauth.net/2.1/",[427],[611],{"type":420,"value":612},"OAuth 2.1 specification",{"type":420,"value":614}," is only supported since 2024.1.",{"type":415,"tag":616,"props":617,"children":619},"h3",{"id":618},"oauth-20-authorization-code-flow-with-pkce",[620],{"type":420,"value":621},"OAuth 2.0 authorization code flow with PKCE",{"type":415,"tag":416,"props":623,"children":624},{},[625,627,634],{"type":420,"value":626},"The OAuth 2.0 flow involved in retrieving a valid access token to make requests to an Azure AD B2C protected API is the authorization code flow with PKCE. There are 2 steps in the ",{"type":415,"tag":423,"props":628,"children":631},{"href":629,"rel":630},"https://learn.microsoft.com/en-us/azure/active-directory-b2c/authorization-code-flow",[427],[632],{"type":420,"value":633},"OAuth 2.0 authorization code flow",{"type":420,"value":635},":",{"type":415,"tag":637,"props":638,"children":639},"ol",{},[640,645],{"type":415,"tag":497,"props":641,"children":642},{},[643],{"type":420,"value":644},"Get an authorization code",{"type":415,"tag":497,"props":646,"children":647},{},[648],{"type":420,"value":649},"Exchange the authorization code for an access token",{"type":415,"tag":416,"props":651,"children":652},{},[653],{"type":420,"value":654},"Step 1 involves the user entering their credentials in the login form (Azure AD B2C login form in this case). At first sight, it might appear not very suitable for using HTTP files but the JetBrains HTTP Client handled it by opening the login form in the IDE embedded browser.",{"type":415,"tag":416,"props":656,"children":657},{},[658],{"type":420,"value":659},"For Azure AD B2C,",{"type":415,"tag":493,"props":661,"children":662},{},[663,674],{"type":415,"tag":497,"props":664,"children":665},{},[666,668],{"type":420,"value":667},"the authorize endpoint is ",{"type":415,"tag":550,"props":669,"children":671},{"className":670},[],[672],{"type":420,"value":673},"https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{policy}/oauth2/v2.0/authorize",{"type":415,"tag":497,"props":675,"children":676},{},[677,679],{"type":420,"value":678},"the token endpoint is ",{"type":415,"tag":550,"props":680,"children":682},{"className":681},[],[683],{"type":420,"value":684},"https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{policy}/oauth2/v2.0/token",{"type":415,"tag":416,"props":686,"children":687},{},[688],{"type":420,"value":689},"where:",{"type":415,"tag":493,"props":691,"children":692},{},[693,704,715],{"type":415,"tag":497,"props":694,"children":695},{},[696,702],{"type":415,"tag":550,"props":697,"children":699},{"className":698},[],[700],{"type":420,"value":701},"tenant",{"type":420,"value":703}," is the name of the Azure AD B2C tenant",{"type":415,"tag":497,"props":705,"children":706},{},[707,713],{"type":415,"tag":550,"props":708,"children":710},{"className":709},[],[711],{"type":420,"value":712},"clientId",{"type":420,"value":714}," is the application ID of the application registered in Azure AD the B2C tenant",{"type":415,"tag":497,"props":716,"children":717},{},[718,724],{"type":415,"tag":550,"props":719,"children":721},{"className":720},[],[722],{"type":420,"value":723},"policy",{"type":420,"value":725}," is the name of the policy created in the Azure AD B2C tenant",{"type":415,"tag":588,"props":727,"children":729},{"icon":728},"i-heroicons-light-bulb",[730],{"type":415,"tag":416,"props":731,"children":732},{},[733,735,741],{"type":420,"value":734},"When using a custom domain in Azure AD B2C, the endpoints are similar but the ",{"type":415,"tag":550,"props":736,"children":738},{"className":737},[],[739],{"type":420,"value":740},"{tenant}.b2clogin.com",{"type":420,"value":742}," part is replaced by the custom domain.",{"type":415,"tag":416,"props":744,"children":745},{},[746,748,754],{"type":420,"value":747},"If you want to better understand how this flow works, there is a nice diagram in ",{"type":415,"tag":423,"props":749,"children":751},{"href":435,"rel":750},[427],[752],{"type":420,"value":753},"Auth0 documentation",{"type":420,"value":755},".",{"type":415,"tag":616,"props":757,"children":759},{"id":758},"configuration-in-the-jetbrains-http-client",[760],{"type":420,"value":761},"Configuration in the JetBrains HTTP Client",{"type":415,"tag":416,"props":763,"children":764},{},[765],{"type":420,"value":766},"To make the authorization code flow work in the HTTP Client, all I have to do is provide the configuration for the Azure AD B2C tenant in the HTTP environment file.",{"type":415,"tag":416,"props":768,"children":769},{},[770],{"type":420,"value":771},"Here is an example of such configuration:",{"type":415,"tag":773,"props":774,"children":778},"pre",{"className":775,"code":776,"language":777,"meta":401,"style":401},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"apiUrl\": \"https://localhost:5001/api\",\n  \"Security\": {\n    \"Auth\": {\n      \"CIAM\": {\n        \"Type\": \"OAuth2\",\n        \"Grant Type\": \"Authorization Code\",\n        \"PKCE\": true,\n        \"Client ID\": \"3a53c90d-20c4-40e9-b440-4825b70374d7\",\n        \"Scope\": \"openid offline_access profile https://mytenant.onmicrosoft.com/security/user.read\",\n        \"Auth URL\": \"https://mytenant.b2clogin.com/mytenant.onmicrosoft.com/b2c_1_sign_in/oauth2/v2.0/authorize\",\n        \"Token URL\": \"https://mytenant.b2clogin.com/mytenant.onmicrosoft.com/b2c_1_sign_in/oauth2/v2.0/token\",\n        \"Redirect URL\": \"https://localhost:8080/oidc-callback\",\n        \"Acquire Automatically\": true\n      }\n    }\n  }\n}\n","json",[779],{"type":415,"tag":550,"props":780,"children":781},{"__ignoreMap":401},[782,794,838,864,891,918,957,995,1021,1059,1097,1135,1173,1211,1237,1246,1255,1264],{"type":415,"tag":783,"props":784,"children":787},"span",{"class":785,"line":786},"line",1,[788],{"type":415,"tag":783,"props":789,"children":791},{"style":790},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[792],{"type":420,"value":793},"{\n",{"type":415,"tag":783,"props":795,"children":797},{"class":785,"line":796},2,[798,803,809,814,818,823,829,833],{"type":415,"tag":783,"props":799,"children":800},{"style":790},[801],{"type":420,"value":802},"  \"",{"type":415,"tag":783,"props":804,"children":806},{"style":805},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[807],{"type":420,"value":808},"apiUrl",{"type":415,"tag":783,"props":810,"children":811},{"style":790},[812],{"type":420,"value":813},"\"",{"type":415,"tag":783,"props":815,"children":816},{"style":790},[817],{"type":420,"value":635},{"type":415,"tag":783,"props":819,"children":820},{"style":790},[821],{"type":420,"value":822}," \"",{"type":415,"tag":783,"props":824,"children":826},{"style":825},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[827],{"type":420,"value":828},"https://localhost:5001/api",{"type":415,"tag":783,"props":830,"children":831},{"style":790},[832],{"type":420,"value":813},{"type":415,"tag":783,"props":834,"children":835},{"style":790},[836],{"type":420,"value":837},",\n",{"type":415,"tag":783,"props":839,"children":841},{"class":785,"line":840},3,[842,846,851,855,859],{"type":415,"tag":783,"props":843,"children":844},{"style":790},[845],{"type":420,"value":802},{"type":415,"tag":783,"props":847,"children":848},{"style":805},[849],{"type":420,"value":850},"Security",{"type":415,"tag":783,"props":852,"children":853},{"style":790},[854],{"type":420,"value":813},{"type":415,"tag":783,"props":856,"children":857},{"style":790},[858],{"type":420,"value":635},{"type":415,"tag":783,"props":860,"children":861},{"style":790},[862],{"type":420,"value":863}," {\n",{"type":415,"tag":783,"props":865,"children":867},{"class":785,"line":866},4,[868,873,879,883,887],{"type":415,"tag":783,"props":869,"children":870},{"style":790},[871],{"type":420,"value":872},"    \"",{"type":415,"tag":783,"props":874,"children":876},{"style":875},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[877],{"type":420,"value":878},"Auth",{"type":415,"tag":783,"props":880,"children":881},{"style":790},[882],{"type":420,"value":813},{"type":415,"tag":783,"props":884,"children":885},{"style":790},[886],{"type":420,"value":635},{"type":415,"tag":783,"props":888,"children":889},{"style":790},[890],{"type":420,"value":863},{"type":415,"tag":783,"props":892,"children":894},{"class":785,"line":893},5,[895,900,906,910,914],{"type":415,"tag":783,"props":896,"children":897},{"style":790},[898],{"type":420,"value":899},"      \"",{"type":415,"tag":783,"props":901,"children":903},{"style":902},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[904],{"type":420,"value":905},"CIAM",{"type":415,"tag":783,"props":907,"children":908},{"style":790},[909],{"type":420,"value":813},{"type":415,"tag":783,"props":911,"children":912},{"style":790},[913],{"type":420,"value":635},{"type":415,"tag":783,"props":915,"children":916},{"style":790},[917],{"type":420,"value":863},{"type":415,"tag":783,"props":919,"children":921},{"class":785,"line":920},6,[922,927,933,937,941,945,949,953],{"type":415,"tag":783,"props":923,"children":924},{"style":790},[925],{"type":420,"value":926},"        \"",{"type":415,"tag":783,"props":928,"children":930},{"style":929},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[931],{"type":420,"value":932},"Type",{"type":415,"tag":783,"props":934,"children":935},{"style":790},[936],{"type":420,"value":813},{"type":415,"tag":783,"props":938,"children":939},{"style":790},[940],{"type":420,"value":635},{"type":415,"tag":783,"props":942,"children":943},{"style":790},[944],{"type":420,"value":822},{"type":415,"tag":783,"props":946,"children":947},{"style":825},[948],{"type":420,"value":393},{"type":415,"tag":783,"props":950,"children":951},{"style":790},[952],{"type":420,"value":813},{"type":415,"tag":783,"props":954,"children":955},{"style":790},[956],{"type":420,"value":837},{"type":415,"tag":783,"props":958,"children":960},{"class":785,"line":959},7,[961,965,970,974,978,982,987,991],{"type":415,"tag":783,"props":962,"children":963},{"style":790},[964],{"type":420,"value":926},{"type":415,"tag":783,"props":966,"children":967},{"style":929},[968],{"type":420,"value":969},"Grant Type",{"type":415,"tag":783,"props":971,"children":972},{"style":790},[973],{"type":420,"value":813},{"type":415,"tag":783,"props":975,"children":976},{"style":790},[977],{"type":420,"value":635},{"type":415,"tag":783,"props":979,"children":980},{"style":790},[981],{"type":420,"value":822},{"type":415,"tag":783,"props":983,"children":984},{"style":825},[985],{"type":420,"value":986},"Authorization Code",{"type":415,"tag":783,"props":988,"children":989},{"style":790},[990],{"type":420,"value":813},{"type":415,"tag":783,"props":992,"children":993},{"style":790},[994],{"type":420,"value":837},{"type":415,"tag":783,"props":996,"children":998},{"class":785,"line":997},8,[999,1003,1008,1012,1016],{"type":415,"tag":783,"props":1000,"children":1001},{"style":790},[1002],{"type":420,"value":926},{"type":415,"tag":783,"props":1004,"children":1005},{"style":929},[1006],{"type":420,"value":1007},"PKCE",{"type":415,"tag":783,"props":1009,"children":1010},{"style":790},[1011],{"type":420,"value":813},{"type":415,"tag":783,"props":1013,"children":1014},{"style":790},[1015],{"type":420,"value":635},{"type":415,"tag":783,"props":1017,"children":1018},{"style":790},[1019],{"type":420,"value":1020}," true,\n",{"type":415,"tag":783,"props":1022,"children":1024},{"class":785,"line":1023},9,[1025,1029,1034,1038,1042,1046,1051,1055],{"type":415,"tag":783,"props":1026,"children":1027},{"style":790},[1028],{"type":420,"value":926},{"type":415,"tag":783,"props":1030,"children":1031},{"style":929},[1032],{"type":420,"value":1033},"Client ID",{"type":415,"tag":783,"props":1035,"children":1036},{"style":790},[1037],{"type":420,"value":813},{"type":415,"tag":783,"props":1039,"children":1040},{"style":790},[1041],{"type":420,"value":635},{"type":415,"tag":783,"props":1043,"children":1044},{"style":790},[1045],{"type":420,"value":822},{"type":415,"tag":783,"props":1047,"children":1048},{"style":825},[1049],{"type":420,"value":1050},"3a53c90d-20c4-40e9-b440-4825b70374d7",{"type":415,"tag":783,"props":1052,"children":1053},{"style":790},[1054],{"type":420,"value":813},{"type":415,"tag":783,"props":1056,"children":1057},{"style":790},[1058],{"type":420,"value":837},{"type":415,"tag":783,"props":1060,"children":1062},{"class":785,"line":1061},10,[1063,1067,1072,1076,1080,1084,1089,1093],{"type":415,"tag":783,"props":1064,"children":1065},{"style":790},[1066],{"type":420,"value":926},{"type":415,"tag":783,"props":1068,"children":1069},{"style":929},[1070],{"type":420,"value":1071},"Scope",{"type":415,"tag":783,"props":1073,"children":1074},{"style":790},[1075],{"type":420,"value":813},{"type":415,"tag":783,"props":1077,"children":1078},{"style":790},[1079],{"type":420,"value":635},{"type":415,"tag":783,"props":1081,"children":1082},{"style":790},[1083],{"type":420,"value":822},{"type":415,"tag":783,"props":1085,"children":1086},{"style":825},[1087],{"type":420,"value":1088},"openid offline_access profile https://mytenant.onmicrosoft.com/security/user.read",{"type":415,"tag":783,"props":1090,"children":1091},{"style":790},[1092],{"type":420,"value":813},{"type":415,"tag":783,"props":1094,"children":1095},{"style":790},[1096],{"type":420,"value":837},{"type":415,"tag":783,"props":1098,"children":1100},{"class":785,"line":1099},11,[1101,1105,1110,1114,1118,1122,1127,1131],{"type":415,"tag":783,"props":1102,"children":1103},{"style":790},[1104],{"type":420,"value":926},{"type":415,"tag":783,"props":1106,"children":1107},{"style":929},[1108],{"type":420,"value":1109},"Auth URL",{"type":415,"tag":783,"props":1111,"children":1112},{"style":790},[1113],{"type":420,"value":813},{"type":415,"tag":783,"props":1115,"children":1116},{"style":790},[1117],{"type":420,"value":635},{"type":415,"tag":783,"props":1119,"children":1120},{"style":790},[1121],{"type":420,"value":822},{"type":415,"tag":783,"props":1123,"children":1124},{"style":825},[1125],{"type":420,"value":1126},"https://mytenant.b2clogin.com/mytenant.onmicrosoft.com/b2c_1_sign_in/oauth2/v2.0/authorize",{"type":415,"tag":783,"props":1128,"children":1129},{"style":790},[1130],{"type":420,"value":813},{"type":415,"tag":783,"props":1132,"children":1133},{"style":790},[1134],{"type":420,"value":837},{"type":415,"tag":783,"props":1136,"children":1138},{"class":785,"line":1137},12,[1139,1143,1148,1152,1156,1160,1165,1169],{"type":415,"tag":783,"props":1140,"children":1141},{"style":790},[1142],{"type":420,"value":926},{"type":415,"tag":783,"props":1144,"children":1145},{"style":929},[1146],{"type":420,"value":1147},"Token URL",{"type":415,"tag":783,"props":1149,"children":1150},{"style":790},[1151],{"type":420,"value":813},{"type":415,"tag":783,"props":1153,"children":1154},{"style":790},[1155],{"type":420,"value":635},{"type":415,"tag":783,"props":1157,"children":1158},{"style":790},[1159],{"type":420,"value":822},{"type":415,"tag":783,"props":1161,"children":1162},{"style":825},[1163],{"type":420,"value":1164},"https://mytenant.b2clogin.com/mytenant.onmicrosoft.com/b2c_1_sign_in/oauth2/v2.0/token",{"type":415,"tag":783,"props":1166,"children":1167},{"style":790},[1168],{"type":420,"value":813},{"type":415,"tag":783,"props":1170,"children":1171},{"style":790},[1172],{"type":420,"value":837},{"type":415,"tag":783,"props":1174,"children":1176},{"class":785,"line":1175},13,[1177,1181,1186,1190,1194,1198,1203,1207],{"type":415,"tag":783,"props":1178,"children":1179},{"style":790},[1180],{"type":420,"value":926},{"type":415,"tag":783,"props":1182,"children":1183},{"style":929},[1184],{"type":420,"value":1185},"Redirect URL",{"type":415,"tag":783,"props":1187,"children":1188},{"style":790},[1189],{"type":420,"value":813},{"type":415,"tag":783,"props":1191,"children":1192},{"style":790},[1193],{"type":420,"value":635},{"type":415,"tag":783,"props":1195,"children":1196},{"style":790},[1197],{"type":420,"value":822},{"type":415,"tag":783,"props":1199,"children":1200},{"style":825},[1201],{"type":420,"value":1202},"https://localhost:8080/oidc-callback",{"type":415,"tag":783,"props":1204,"children":1205},{"style":790},[1206],{"type":420,"value":813},{"type":415,"tag":783,"props":1208,"children":1209},{"style":790},[1210],{"type":420,"value":837},{"type":415,"tag":783,"props":1212,"children":1214},{"class":785,"line":1213},14,[1215,1219,1224,1228,1232],{"type":415,"tag":783,"props":1216,"children":1217},{"style":790},[1218],{"type":420,"value":926},{"type":415,"tag":783,"props":1220,"children":1221},{"style":929},[1222],{"type":420,"value":1223},"Acquire Automatically",{"type":415,"tag":783,"props":1225,"children":1226},{"style":790},[1227],{"type":420,"value":813},{"type":415,"tag":783,"props":1229,"children":1230},{"style":790},[1231],{"type":420,"value":635},{"type":415,"tag":783,"props":1233,"children":1234},{"style":790},[1235],{"type":420,"value":1236}," true\n",{"type":415,"tag":783,"props":1238,"children":1240},{"class":785,"line":1239},15,[1241],{"type":415,"tag":783,"props":1242,"children":1243},{"style":790},[1244],{"type":420,"value":1245},"      }\n",{"type":415,"tag":783,"props":1247,"children":1249},{"class":785,"line":1248},16,[1250],{"type":415,"tag":783,"props":1251,"children":1252},{"style":790},[1253],{"type":420,"value":1254},"    }\n",{"type":415,"tag":783,"props":1256,"children":1258},{"class":785,"line":1257},17,[1259],{"type":415,"tag":783,"props":1260,"children":1261},{"style":790},[1262],{"type":420,"value":1263},"  }\n",{"type":415,"tag":783,"props":1265,"children":1267},{"class":785,"line":1266},18,[1268],{"type":415,"tag":783,"props":1269,"children":1270},{"style":790},[1271],{"type":420,"value":1272},"}\n",{"type":415,"tag":588,"props":1274,"children":1275},{"icon":728},[1276],{"type":415,"tag":416,"props":1277,"children":1278},{},[1279],{"type":420,"value":1280},"Instead of setting PKCE to true, you can set if to a JSON object with the code challenge method and code verifier to use in it.",{"type":415,"tag":588,"props":1282,"children":1283},{"icon":590},[1284],{"type":415,"tag":416,"props":1285,"children":1286},{},[1287],{"type":420,"value":1288},"In this example, I have set a local Redirect URL as my front was running locally. But I could also have set the Redirect URL to another environment where my web application is running.",{"type":415,"tag":416,"props":1290,"children":1291},{},[1292,1294,1300],{"type":420,"value":1293},"You can check the ",{"type":415,"tag":423,"props":1295,"children":1297},{"href":435,"rel":1296},[427],[1298],{"type":420,"value":1299},"JetBrains documentation",{"type":420,"value":1301}," to have more information about the HTTP Client support for OAuth 2.0 authorization.",{"type":415,"tag":616,"props":1303,"children":1305},{"id":1304},"authenticated-http-requests-in-the-http-file",[1306],{"type":420,"value":1307},"Authenticated HTTP Requests in the HTTP file",{"type":415,"tag":416,"props":1309,"children":1310},{},[1311],{"type":420,"value":1312},"Once the configuration is set, retrieving an access token can be done with a simple click in the configuration file.",{"type":415,"tag":416,"props":1314,"children":1315},{},[1316],{"type":420,"value":1317},"The authentication process is logged so we can check the requests made and identify any mistakes made in the configuration.",{"type":415,"tag":416,"props":1319,"children":1320},{},[1321],{"type":415,"tag":1322,"props":1323,"children":1329},"img",{"alt":1324,"className":1325,"src":1328},"HTTP authentication log",[1326,1327],"rounded-lg","mx-auto","/posts/images/httpclientsoauht2_1.webp",[],{"type":415,"tag":416,"props":1331,"children":1332},{},[1333,1335,1341],{"type":420,"value":1334},"Hopefully, we don't have to manually retrieve an access token each time we need to execute an HTTP request in an HTTP file of our IDE. We can just use the ",{"type":415,"tag":550,"props":1336,"children":1338},{"className":1337},[],[1339],{"type":420,"value":1340},"{{$auth.token()}}",{"type":420,"value":1342}," variable in the Authorization header of our requests, like this:",{"type":415,"tag":773,"props":1344,"children":1347},{"className":1345,"code":1346,"language":212,"meta":401,"style":401},"language-http shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","GET {{apiUrl}}/products\nAuthorization: Bearer {{$auth.token(\"CIAM\")}}\n",[1348],{"type":415,"tag":550,"props":1349,"children":1350},{"__ignoreMap":401},[1351,1366],{"type":415,"tag":783,"props":1352,"children":1353},{"class":785,"line":786},[1354,1360],{"type":415,"tag":783,"props":1355,"children":1357},{"style":1356},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF;--shiki-light-font-style:italic;--shiki-default-font-style:italic;--shiki-dark-font-style:italic",[1358],{"type":420,"value":1359},"GET",{"type":415,"tag":783,"props":1361,"children":1363},{"style":1362},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[1364],{"type":420,"value":1365}," {{apiUrl}}/products\n",{"type":415,"tag":783,"props":1367,"children":1368},{"class":785,"line":796},[1369,1374,1378],{"type":415,"tag":783,"props":1370,"children":1371},{"style":929},[1372],{"type":420,"value":1373},"Authorization",{"type":415,"tag":783,"props":1375,"children":1376},{"style":902},[1377],{"type":420,"value":635},{"type":415,"tag":783,"props":1379,"children":1380},{"style":825},[1381],{"type":420,"value":1382}," Bearer {{$auth.token(\"CIAM\")}}\n",{"type":415,"tag":416,"props":1384,"children":1385},{},[1386],{"type":420,"value":1387},"The IDE will handle the rest for us.",{"type":415,"tag":443,"props":1389,"children":1391},{"id":1390},"wrapping-up",[1392],{"type":420,"value":1393},"Wrapping up",{"type":415,"tag":416,"props":1395,"children":1396},{},[1397],{"type":420,"value":1398},"The HTTP Client OAuth 2.0 feature in JetBrains IDEs has greatly simplified making authenticated HTTP requests to secure APIs. While this article focused on Azure AD B2C, the same principles apply to other Authorization Servers, with only the authorize and token endpoints differing.",{"type":415,"tag":416,"props":1400,"children":1401},{},[1402,1404,1410,1412,1417],{"type":420,"value":1403},"I hope other IDEs will adopt this feature, using the same convention for the ",{"type":415,"tag":550,"props":1405,"children":1407},{"className":1406},[],[1408],{"type":420,"value":1409},"$auth.token()",{"type":420,"value":1411}," variable and its configuration. The only drawback is for developers not using JetBrains IDEs, who will need to adjust requests containing the ",{"type":415,"tag":550,"props":1413,"children":1415},{"className":1414},[],[1416],{"type":420,"value":1409},{"type":420,"value":1418}," variable to run them in their IDEs.",{"type":415,"tag":1420,"props":1421,"children":1422},"style",{},[1423],{"type":420,"value":1424},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":401,"searchDepth":796,"depth":796,"links":1426},[1427,1428,1429,1434],{"id":445,"depth":796,"text":448},{"id":483,"depth":796,"text":486},{"id":578,"depth":796,"text":581,"children":1430},[1431,1432,1433],{"id":618,"depth":840,"text":621},{"id":758,"depth":840,"text":761},{"id":1304,"depth":840,"text":1307},{"id":1390,"depth":796,"text":1393},"markdown","content:1.posts:61.http-clients-oauth2.md","content","1.posts/61.http-clients-oauth2.md","md",{"_path":163,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":162,"description":1441,"lead":1442,"date":1443,"badge":1444,"image":1445,"tags":1447,"canonical":1448,"body":1449,"_type":1435,"_id":1653,"_source":1437,"_file":1654,"_extension":1439},"You may have come across pnpm through discussions with fellow developers, reading blog posts, watching videos, or attending developer conferences. You have probably heard its praises: it's fast, disk-space efficient, and great for monorepos.","Discussion about pnpm usage and popularity.","2023-07-06T00:00:00.000Z",{"label":408},{"src":1446},"/images/pnpm.png",[362,296,206],"https://bordeauxcoders.com/who-is-using-pnpm",{"type":412,"children":1450,"toc":1647},[1451,1455,1460,1466,1471,1483,1488,1497,1502,1508,1513,1522,1527,1536,1542,1547,1552,1560,1565,1603,1608,1616,1622,1627,1632,1637,1642],{"type":415,"tag":416,"props":1452,"children":1453},{},[1454],{"type":420,"value":1441},{"type":415,"tag":416,"props":1456,"children":1457},{},[1458],{"type":420,"value":1459},"However, you might wonder: who is actually using pnpm?",{"type":415,"tag":443,"props":1461,"children":1463},{"id":1462},"a-growing-popularity",[1464],{"type":420,"value":1465},"A growing popularity",{"type":415,"tag":416,"props":1467,"children":1468},{},[1469],{"type":420,"value":1470},"At the time of writing, pnpm has over 24k stars on GitHub, and this number is rapidly increasing. The pnpm Twitter account maintains a thread that tracks the number of stars. Each time the GitHub repository gains 1k stars, a new tweet is posted. For quite some time now, it has been growing by 1K every two months.",{"type":415,"tag":1472,"props":1473,"children":1477},"div",{"className":1474},[1475,1476],"flex","justify-center",[1478],{"type":415,"tag":1479,"props":1480,"children":1482},"tweet",{"id":1481},"1666004997840986116",[],{"type":415,"tag":416,"props":1484,"children":1485},{},[1486],{"type":420,"value":1487},"Another indicator of its growing popularity is its number of downloads. If you go to npm stats you can see how this number evolved compared to npm and yarn.",{"type":415,"tag":416,"props":1489,"children":1490},{},[1491],{"type":415,"tag":1322,"props":1492,"children":1496},{"alt":1493,"className":1494,"src":1495},"npm vs yarn vs pnpm downloads per day",[1326,1327],"/posts/images/pnpm101_whouses_stats.webp",[],{"type":415,"tag":416,"props":1498,"children":1499},{},[1500],{"type":420,"value":1501},"I believe this diagram speaks for itself 🚀.",{"type":415,"tag":443,"props":1503,"children":1505},{"id":1504},"which-companies-are-using-pnpm",[1506],{"type":420,"value":1507},"Which companies are using pnpm?",{"type":415,"tag":416,"props":1509,"children":1510},{},[1511],{"type":420,"value":1512},"There is a page on pnpm's documentation about well-known companies using pnpm.",{"type":415,"tag":416,"props":1514,"children":1515},{},[1516],{"type":415,"tag":1322,"props":1517,"children":1521},{"alt":1518,"className":1519,"src":1520},"Screeshot of the documentation showing companies using pnpm",[1326,1327],"/posts/images/pnpm101_whouses_companies.webp",[],{"type":415,"tag":416,"props":1523,"children":1524},{},[1525],{"type":420,"value":1526},"You can also see some other companies on the StackShare website (but it seems not many companies took the time to fill in the fact that they were using pnpm in their stack).",{"type":415,"tag":416,"props":1528,"children":1529},{},[1530],{"type":415,"tag":1322,"props":1531,"children":1535},{"alt":1532,"className":1533,"src":1534},"Screeshot of the StackShare page showing companies using pnpm",[1326,1327],"/posts/images/pnpm101_whouses_companies_2.webp",[],{"type":415,"tag":443,"props":1537,"children":1539},{"id":1538},"which-popular-open-source-projects-are-using-pnpm",[1540],{"type":420,"value":1541},"Which popular open-source projects are using pnpm?",{"type":415,"tag":416,"props":1543,"children":1544},{},[1545],{"type":420,"value":1546},"If you see a pnpm-lock.yaml or a pnpm-workspace.yaml file in a GitHub repository, then that project is definitively using pnpm to manage its dependencies. You can use this technique to find GitHub projects using pnpm by querying them with GitHub code search.",{"type":415,"tag":416,"props":1548,"children":1549},{},[1550],{"type":420,"value":1551},"I thought it would be interesting to explore which package managers are utilized in the development of popular JavaScript framework projects. And guess what? Many JavaScript frameworks are developed using pnpm 💖.",{"type":415,"tag":588,"props":1553,"children":1554},{"icon":728},[1555],{"type":415,"tag":416,"props":1556,"children":1557},{},[1558],{"type":420,"value":1559},"To check that these projects were using pnpm, I not only verify the presence of pnpm specific files but also checked their continuous integration pipelines (contained in the .github folder) to see what they were using to manage their dependencies.",{"type":415,"tag":416,"props":1561,"children":1562},{},[1563],{"type":420,"value":1564},"Here is a non-exhaustive list of popular JavaScript web frameworks that use pnpm as their package manager:",{"type":415,"tag":493,"props":1566,"children":1567},{},[1568,1573,1578,1583,1588,1593,1598],{"type":415,"tag":497,"props":1569,"children":1570},{},[1571],{"type":420,"value":1572},"Vue",{"type":415,"tag":497,"props":1574,"children":1575},{},[1576],{"type":420,"value":1577},"Nuxt",{"type":415,"tag":497,"props":1579,"children":1580},{},[1581],{"type":420,"value":1582},"Next.js",{"type":415,"tag":497,"props":1584,"children":1585},{},[1586],{"type":420,"value":1587},"SvelteKit",{"type":415,"tag":497,"props":1589,"children":1590},{},[1591],{"type":420,"value":1592},"SolidStart",{"type":415,"tag":497,"props":1594,"children":1595},{},[1596],{"type":420,"value":1597},"Astro",{"type":415,"tag":497,"props":1599,"children":1600},{},[1601],{"type":420,"value":1602},"Qwik",{"type":415,"tag":416,"props":1604,"children":1605},{},[1606],{"type":420,"value":1607},"That's quite an impressive list: most modern JavaScript web frameworks seem to have chosen pnpm. That's also the case for popular frontend tooling projects like Vite or Turbo.",{"type":415,"tag":588,"props":1609,"children":1610},{"icon":728},[1611],{"type":415,"tag":416,"props":1612,"children":1613},{},[1614],{"type":420,"value":1615},"The fact that pnpm is utilized by maintainers for internal development of these frameworks does not imply that these frameworks can only be used with pnpm. Typically, JavaScript frameworks are \"package manager\" agnostic, allowing you to use your preferred package manager when developing a project with one of these frameworks.",{"type":415,"tag":443,"props":1617,"children":1619},{"id":1618},"should-you-use-pnpm-because-others-do",[1620],{"type":420,"value":1621},"Should you use pnpm because others do?",{"type":415,"tag":416,"props":1623,"children":1624},{},[1625],{"type":420,"value":1626},"Short answer: no.",{"type":415,"tag":416,"props":1628,"children":1629},{},[1630],{"type":420,"value":1631},"Choosing a technology solely based on its popularity is not advisable. While popularity is a factor to consider, it should not be the only determining aspect. Thus, you should not use pnpm because well-known companies or popular open-source projects use it.",{"type":415,"tag":416,"props":1633,"children":1634},{},[1635],{"type":420,"value":1636},"However (and here's the long answer 😉), you should consider exploring pnpm, as there must be a reason why all these intelligent individuals have chosen it over npm or yarn. Investigate the issues pnpm resolves for them; perhaps you face similar challenges in your projects. See what problems pnpm solves for them, maybe you have the same problems in your projects. You might not even be aware of certain problems (such as lengthy CI due to time-consuming package installations, excessive space occupied by node modules, or issues with hoisted node modules), but pnpm could potentially make some things easier. Nevertheless, if you are satisfied with your current package manager, there is no need to switch just to imitate the popular frameworks projects.",{"type":415,"tag":416,"props":1638,"children":1639},{},[1640],{"type":420,"value":1641},"I believe people are familiar with npm since it is the default package manager for Node.js projects. They might also know about yarn because it was initially developed by Facebook (who created React) and addressed some issues with npm. However, people recognize and utilize pnpm due to its performance and ability to resolve the problems they might encounter with npm package management. That's also why I use pnpm; it does the job, and it does it quickly.",{"type":415,"tag":416,"props":1643,"children":1644},{},[1645],{"type":420,"value":1646},"Now you know that you're not alone in using pnpm; from renowned companies to popular open-source projects, many people are utilizing it.",{"title":401,"searchDepth":796,"depth":796,"links":1648},[1649,1650,1651,1652],{"id":1462,"depth":796,"text":1465},{"id":1504,"depth":796,"text":1507},{"id":1538,"depth":796,"text":1541},{"id":1618,"depth":796,"text":1621},"content:1.posts:52.pnpm-who-is-using.md","1.posts/52.pnpm-who-is-using.md",{"_path":157,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":156,"description":1656,"lead":1657,"date":1658,"image":1659,"badge":1660,"tags":1661,"canonical":1662,"body":1663,"_type":1435,"_id":1832,"_source":1437,"_file":1833,"_extension":1439},"You have a dependency in your project and want to execute a command using it? The pnpm exec command can help you with that.","pnpm exec","2023-06-15T00:00:00.000Z",{"src":1446},{"label":408},[362,296,206],"https://bordeauxcoders.com/execute-commands-using-your-project-dependencies",{"type":412,"children":1664,"toc":1827},[1665,1678,1684,1726,1738,1744,1770,1776,1781,1811,1823],{"type":415,"tag":416,"props":1666,"children":1667},{},[1668,1670,1676],{"type":420,"value":1669},"You have a dependency in your project and want to execute a command using it? The ",{"type":415,"tag":423,"props":1671,"children":1674},{"href":1672,"rel":1673},"https://pnpm.io/cli/exec",[427],[1675],{"type":420,"value":1657},{"type":420,"value":1677}," command can help you with that.",{"type":415,"tag":443,"props":1679,"children":1681},{"id":1680},"an-example",[1682],{"type":420,"value":1683},"An example",{"type":415,"tag":773,"props":1685,"children":1689},{"className":1686,"code":1687,"language":1688,"meta":401,"style":401},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight"," pnpm exec eslint . --ext .ts\n","bash",[1690],{"type":415,"tag":550,"props":1691,"children":1692},{"__ignoreMap":401},[1693],{"type":415,"tag":783,"props":1694,"children":1695},{"class":785,"line":786},[1696,1701,1706,1711,1716,1721],{"type":415,"tag":783,"props":1697,"children":1698},{"style":875},[1699],{"type":420,"value":1700}," pnpm",{"type":415,"tag":783,"props":1702,"children":1703},{"style":825},[1704],{"type":420,"value":1705}," exec",{"type":415,"tag":783,"props":1707,"children":1708},{"style":825},[1709],{"type":420,"value":1710}," eslint",{"type":415,"tag":783,"props":1712,"children":1713},{"style":825},[1714],{"type":420,"value":1715}," .",{"type":415,"tag":783,"props":1717,"children":1718},{"style":825},[1719],{"type":420,"value":1720}," --ext",{"type":415,"tag":783,"props":1722,"children":1723},{"style":825},[1724],{"type":420,"value":1725}," .ts\n",{"type":415,"tag":416,"props":1727,"children":1728},{},[1729,1731,1736],{"type":420,"value":1730},"Given that ESLint is a project dependency, this example shows how to use the ",{"type":415,"tag":550,"props":1732,"children":1734},{"className":1733},[],[1735],{"type":420,"value":1657},{"type":420,"value":1737}," command to run the ESLint tool on all TypeScript files within the project.",{"type":415,"tag":443,"props":1739,"children":1741},{"id":1740},"some-use-cases",[1742],{"type":420,"value":1743},"Some use cases",{"type":415,"tag":493,"props":1745,"children":1746},{},[1747,1752,1757],{"type":415,"tag":497,"props":1748,"children":1749},{},[1750],{"type":420,"value":1751},"You need to do a specific command that is not part of your npm scripts",{"type":415,"tag":497,"props":1753,"children":1754},{},[1755],{"type":420,"value":1756},"You want to execute a tool that is a dependency of your project without having to install it globally",{"type":415,"tag":497,"props":1758,"children":1759},{},[1760,1762,1768],{"type":420,"value":1761},"You need to execute a CLI package command in a CI pipeline, and this package is already included in the ",{"type":415,"tag":550,"props":1763,"children":1765},{"className":1764},[],[1766],{"type":420,"value":1767},"devDependencies",{"type":420,"value":1769}," of your project.",{"type":415,"tag":443,"props":1771,"children":1773},{"id":1772},"good-to-know",[1774],{"type":420,"value":1775},"Good to know",{"type":415,"tag":416,"props":1777,"children":1778},{},[1779],{"type":420,"value":1780},"If the command you are using does not conflict with a built-in pnpm command, there is no need to specify 'exec'. Referring to the previous example, you can simply run:",{"type":415,"tag":773,"props":1782,"children":1784},{"className":1686,"code":1783,"language":1688,"meta":401,"style":401}," pnpm eslint . --ext .ts\n",[1785],{"type":415,"tag":550,"props":1786,"children":1787},{"__ignoreMap":401},[1788],{"type":415,"tag":783,"props":1789,"children":1790},{"class":785,"line":786},[1791,1795,1799,1803,1807],{"type":415,"tag":783,"props":1792,"children":1793},{"style":875},[1794],{"type":420,"value":1700},{"type":415,"tag":783,"props":1796,"children":1797},{"style":825},[1798],{"type":420,"value":1710},{"type":415,"tag":783,"props":1800,"children":1801},{"style":825},[1802],{"type":420,"value":1715},{"type":415,"tag":783,"props":1804,"children":1805},{"style":825},[1806],{"type":420,"value":1720},{"type":415,"tag":783,"props":1808,"children":1809},{"style":825},[1810],{"type":420,"value":1725},{"type":415,"tag":416,"props":1812,"children":1813},{},[1814,1816,1821],{"type":420,"value":1815},"It's one of the small details that make using ",{"type":415,"tag":550,"props":1817,"children":1819},{"className":1818},[],[1820],{"type":420,"value":362},{"type":420,"value":1822}," so pleasant.",{"type":415,"tag":1420,"props":1824,"children":1825},{},[1826],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":1828},[1829,1830,1831],{"id":1680,"depth":796,"text":1683},{"id":1740,"depth":796,"text":1743},{"id":1772,"depth":796,"text":1775},"content:1.posts:50.pnpm-exec.md","1.posts/50.pnpm-exec.md",{"_path":151,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":150,"description":1835,"lead":1836,"date":1837,"image":1838,"badge":1839,"tags":1840,"canonical":1841,"body":1842,"_type":1435,"_id":2008,"_source":1437,"_file":2009,"_extension":1439},"If you are working on various projects, you have likely encountered situations where you need to have multiple versions of Node.js installed on your computer.","pnpm env","2023-05-25T00:00:00.000Z",{"src":1446},{"label":408},[362,296,206],"https://bordeauxcoders.com/manage-multiple-nodejs-versions",{"type":412,"children":1843,"toc":2003},[1844,1848,1865,1869,1903,1908,1913,1919,1924,1938,1942,1975,1999],{"type":415,"tag":416,"props":1845,"children":1846},{},[1847],{"type":420,"value":1835},{"type":415,"tag":416,"props":1849,"children":1850},{},[1851,1853,1863],{"type":420,"value":1852},"You might not know it, but managing multiple Node.js versions is something you can do with pnpm, using the ",{"type":415,"tag":423,"props":1854,"children":1857},{"href":1855,"rel":1856},"https://pnpm.io/fr/cli/env",[427],[1858],{"type":415,"tag":550,"props":1859,"children":1861},{"className":1860},[],[1862],{"type":420,"value":1836},{"type":420,"value":1864}," command.",{"type":415,"tag":443,"props":1866,"children":1867},{"id":1680},[1868],{"type":420,"value":1683},{"type":415,"tag":773,"props":1870,"children":1872},{"className":1686,"code":1871,"language":1688,"meta":401,"style":401},"pnpm env use -g lts \n",[1873],{"type":415,"tag":550,"props":1874,"children":1875},{"__ignoreMap":401},[1876],{"type":415,"tag":783,"props":1877,"children":1878},{"class":785,"line":786},[1879,1883,1888,1893,1898],{"type":415,"tag":783,"props":1880,"children":1881},{"style":875},[1882],{"type":420,"value":362},{"type":415,"tag":783,"props":1884,"children":1885},{"style":825},[1886],{"type":420,"value":1887}," env",{"type":415,"tag":783,"props":1889,"children":1890},{"style":825},[1891],{"type":420,"value":1892}," use",{"type":415,"tag":783,"props":1894,"children":1895},{"style":825},[1896],{"type":420,"value":1897}," -g",{"type":415,"tag":783,"props":1899,"children":1900},{"style":825},[1901],{"type":420,"value":1902}," lts\n",{"type":415,"tag":416,"props":1904,"children":1905},{},[1906],{"type":420,"value":1907},"This example demonstrates how to install the LTS version of Node.js.",{"type":415,"tag":416,"props":1909,"children":1910},{},[1911],{"type":420,"value":1912},"Additionally, you can install specific versions of Node.js, view the versions already present on your computer, or remove one.",{"type":415,"tag":443,"props":1914,"children":1916},{"id":1915},"why-use-pnpm-as-your-node-version-manager",[1917],{"type":420,"value":1918},"Why use pnpm as your Node version manager?",{"type":415,"tag":416,"props":1920,"children":1921},{},[1922],{"type":420,"value":1923},"Because managing your Node.js version is built into pnpm: if you already use pnpm as your npm package manager, you don't need to install another tool.",{"type":415,"tag":416,"props":1925,"children":1926},{},[1927,1929,1936],{"type":420,"value":1928},"But there is absolutely nothing wrong with using another Node version manager if you prefer. I was using ",{"type":415,"tag":423,"props":1930,"children":1933},{"href":1931,"rel":1932},"https://github.com/coreybutler/nvm-windows",[427],[1934],{"type":420,"value":1935},"nvm-windows",{"type":420,"value":1937}," before and I was happy with it. I just don't see the point of installing it anymore as similar functionality is already available in pnpm.",{"type":415,"tag":443,"props":1939,"children":1940},{"id":1772},[1941],{"type":420,"value":1775},{"type":415,"tag":416,"props":1943,"children":1944},{},[1945,1947,1958,1960,1966,1973],{"type":420,"value":1946},"To specify a Node.js version to use in a project/folder, you can add an ",{"type":415,"tag":423,"props":1948,"children":1951},{"href":1949,"rel":1950},"https://pnpm.io/fr/npmrc",[427],[1952],{"type":415,"tag":550,"props":1953,"children":1955},{"className":1954},[],[1956],{"type":420,"value":1957},".npmrc",{"type":420,"value":1959}," file ",{"type":415,"tag":550,"props":1961,"children":1963},{"className":1962},[],[1964],{"type":420,"value":1965},"use-node-version",{"type":415,"tag":423,"props":1967,"children":1970},{"href":1968,"rel":1969},"https://pnpm.io/fr/npmrc#use-node-version",[427],[1971],{"type":420,"value":1972},"​",{"type":420,"value":1974}," setting, like that:",{"type":415,"tag":773,"props":1976,"children":1978},{"className":1686,"code":1977,"language":1688,"meta":401,"style":401},"use-node-version=16.16.0\n",[1979],{"type":415,"tag":550,"props":1980,"children":1981},{"__ignoreMap":401},[1982],{"type":415,"tag":783,"props":1983,"children":1984},{"class":785,"line":786},[1985,1989,1994],{"type":415,"tag":783,"props":1986,"children":1987},{"style":1362},[1988],{"type":420,"value":1965},{"type":415,"tag":783,"props":1990,"children":1991},{"style":790},[1992],{"type":420,"value":1993},"=",{"type":415,"tag":783,"props":1995,"children":1996},{"style":825},[1997],{"type":420,"value":1998},"16.16.0\n",{"type":415,"tag":1420,"props":2000,"children":2001},{},[2002],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":2004},[2005,2006,2007],{"id":1680,"depth":796,"text":1683},{"id":1915,"depth":796,"text":1918},{"id":1772,"depth":796,"text":1775},"content:1.posts:48.pnpm-env.md","1.posts/48.pnpm-env.md",{"_path":148,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":147,"description":2011,"lead":2012,"date":2013,"image":2014,"badge":2015,"tags":2016,"canonical":2017,"body":2018,"_type":1435,"_id":2202,"_source":1437,"_file":2203,"_extension":1439},"Sometimes, all you want to do is grab an npm package and execute a command with it, without having to install it (whether globally or as a dependency).","pnpm dlx","2023-05-18T00:00:00.000Z",{"src":1446},{"label":408},[362,296,206],"https://bordeauxcoders.com/perform-dynamic-execution-of-an-npm-package",{"type":412,"children":2019,"toc":2197},[2020,2024,2040,2044,2073,2093,2106,2110,2149,2153,2193],{"type":415,"tag":416,"props":2021,"children":2022},{},[2023],{"type":420,"value":2011},{"type":415,"tag":416,"props":2025,"children":2026},{},[2027,2029,2039],{"type":420,"value":2028},"That's what you can do with ",{"type":415,"tag":423,"props":2030,"children":2033},{"href":2031,"rel":2032},"https://pnpm.io/cli/dlx",[427],[2034],{"type":415,"tag":550,"props":2035,"children":2037},{"className":2036},[],[2038],{"type":420,"value":2012},{"type":420,"value":755},{"type":415,"tag":443,"props":2041,"children":2042},{"id":1680},[2043],{"type":420,"value":1683},{"type":415,"tag":773,"props":2045,"children":2047},{"className":1686,"code":2046,"language":1688,"meta":401,"style":401},"pnpm dlx vercel deploy\n",[2048],{"type":415,"tag":550,"props":2049,"children":2050},{"__ignoreMap":401},[2051],{"type":415,"tag":783,"props":2052,"children":2053},{"class":785,"line":786},[2054,2058,2063,2068],{"type":415,"tag":783,"props":2055,"children":2056},{"style":875},[2057],{"type":420,"value":362},{"type":415,"tag":783,"props":2059,"children":2060},{"style":825},[2061],{"type":420,"value":2062}," dlx",{"type":415,"tag":783,"props":2064,"children":2065},{"style":825},[2066],{"type":420,"value":2067}," vercel",{"type":415,"tag":783,"props":2069,"children":2070},{"style":825},[2071],{"type":420,"value":2072}," deploy\n",{"type":415,"tag":416,"props":2074,"children":2075},{},[2076,2078,2085,2087,2092],{"type":420,"value":2077},"This example shows how to use the ",{"type":415,"tag":423,"props":2079,"children":2082},{"href":2080,"rel":2081},"https://vercel.com/docs/cli",[427],[2083],{"type":420,"value":2084},"vercel CLI package",{"type":420,"value":2086}," without having to install it thanks to ",{"type":415,"tag":550,"props":2088,"children":2090},{"className":2089},[],[2091],{"type":420,"value":2012},{"type":420,"value":755},{"type":415,"tag":416,"props":2094,"children":2095},{},[2096,2098,2104],{"type":420,"value":2097},"In this example, pnpm downloads the vercel package, and executes it with the command ",{"type":415,"tag":550,"props":2099,"children":2101},{"className":2100},[],[2102],{"type":420,"value":2103},"deploy",{"type":420,"value":2105}," (that deploys a project to the Vercel platform).",{"type":415,"tag":443,"props":2107,"children":2108},{"id":1740},[2109],{"type":420,"value":1743},{"type":415,"tag":493,"props":2111,"children":2112},{},[2113,2118,2123,2128],{"type":415,"tag":497,"props":2114,"children":2115},{},[2116],{"type":420,"value":2117},"You don't want to install globally a package because you only need to execute its binary script once",{"type":415,"tag":497,"props":2119,"children":2120},{},[2121],{"type":420,"value":2122},"You don't want a package to be a dev dependency of your project, or you are not using it in the context of a Node project",{"type":415,"tag":497,"props":2124,"children":2125},{},[2126],{"type":420,"value":2127},"You need to execute a CLI package command from a CI pipeline",{"type":415,"tag":497,"props":2129,"children":2130},{},[2131,2133,2139,2141,2147],{"type":420,"value":2132},"You want to ensure you use the latest version of a package (useful for starter kits like ",{"type":415,"tag":550,"props":2134,"children":2136},{"className":2135},[],[2137],{"type":420,"value":2138},"create-vite",{"type":420,"value":2140},", or ",{"type":415,"tag":550,"props":2142,"children":2144},{"className":2143},[],[2145],{"type":420,"value":2146},"create-vue",{"type":420,"value":2148},")",{"type":415,"tag":443,"props":2150,"children":2151},{"id":1772},[2152],{"type":420,"value":1775},{"type":415,"tag":416,"props":2154,"children":2155},{},[2156,2158,2169,2171,2176,2178,2184,2186,2192],{"type":420,"value":2157},"For starter kits, you can use ",{"type":415,"tag":423,"props":2159,"children":2162},{"href":2160,"rel":2161},"https://pnpm.io/cli/create",[427],[2163],{"type":415,"tag":550,"props":2164,"children":2166},{"className":2165},[],[2167],{"type":420,"value":2168},"pnpm create",{"type":420,"value":2170}," instead of ",{"type":415,"tag":550,"props":2172,"children":2174},{"className":2173},[],[2175],{"type":420,"value":2012},{"type":420,"value":2177},". For instance, executing ",{"type":415,"tag":550,"props":2179,"children":2181},{"className":2180},[],[2182],{"type":420,"value":2183},"pnpm create vue",{"type":420,"value":2185}," is equivalent to executing ",{"type":415,"tag":550,"props":2187,"children":2189},{"className":2188},[],[2190],{"type":420,"value":2191},"pnpm dlx create-vue",{"type":420,"value":755},{"type":415,"tag":1420,"props":2194,"children":2195},{},[2196],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":2198},[2199,2200,2201],{"id":1680,"depth":796,"text":1683},{"id":1740,"depth":796,"text":1743},{"id":1772,"depth":796,"text":1775},"content:1.posts:47.pnpm-dlx.md","1.posts/47.pnpm-dlx.md",{"_path":139,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":138,"description":2205,"lead":2205,"date":2206,"image":2207,"badge":2209,"tags":2211,"body":2212,"_type":1435,"_id":2456,"_source":1437,"_file":2457,"_extension":1439},"Some tips about .NET, pnpm, and Azure DevOps.","2022-11-20T00:00:00.000Z",{"src":2208},"/images/surface_1.jpg",{"label":2210},"Tips",[272,239,293,362,296,208,343,206],{"type":412,"children":2213,"toc":2451},[2214,2220,2234,2255,2278,2284,2315,2320,2328,2340,2349,2357,2362,2372,2380,2394,2400,2405,2415,2433,2442,2447],{"type":415,"tag":443,"props":2215,"children":2217},{"id":2216},"net-tip-of-the-week-install-net-7-using-winget",[2218],{"type":420,"value":2219},".NET tip of the week: install .NET 7 using winget",{"type":415,"tag":416,"props":2221,"children":2222},{},[2223,2225,2232],{"type":420,"value":2224},"This week, I installed .NET 7 on my laptop and I used ",{"type":415,"tag":423,"props":2226,"children":2229},{"href":2227,"rel":2228},"https://learn.microsoft.com/en-us/windows/package-manager/",[427],[2230],{"type":420,"value":2231},"Windows Package Manager",{"type":420,"value":2233}," for that:",{"type":415,"tag":773,"props":2235,"children":2238},{"className":2236,"code":2237,"language":248,"meta":401,"style":401},"language-powershell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","winget install Microsoft.DotNet.SDK.7\n",[2239],{"type":415,"tag":550,"props":2240,"children":2241},{"__ignoreMap":401},[2242],{"type":415,"tag":783,"props":2243,"children":2244},{"class":785,"line":786},[2245,2250],{"type":415,"tag":783,"props":2246,"children":2247},{"style":1362},[2248],{"type":420,"value":2249},"winget install Microsoft.DotNet.SDK.",{"type":415,"tag":783,"props":2251,"children":2252},{"style":902},[2253],{"type":420,"value":2254},"7\n",{"type":415,"tag":416,"props":2256,"children":2257},{},[2258,2260,2267,2269,2276],{"type":420,"value":2259},"I like winget, I have already written a few articles about it (you can find them ",{"type":415,"tag":423,"props":2261,"children":2264},{"href":2262,"rel":2263},"https://www.techwatching.dev/tags/winget/",[427],[2265],{"type":420,"value":2266},"here",{"type":420,"value":2268},") so I am really glad to see that we can now use winget to install .NET (whether it be the SDKs or the runtimes). You can check ",{"type":415,"tag":423,"props":2270,"children":2273},{"href":2271,"rel":2272},"https://devblogs.microsoft.com/dotnet/dotnet-now-on-windows-package-manager/",[427],[2274],{"type":420,"value":2275},"Microsoft's article",{"type":420,"value":2277}," announcing it for more information.",{"type":415,"tag":443,"props":2279,"children":2281},{"id":2280},"tool-of-the-week-pnpm",[2282],{"type":420,"value":2283},"Tool of the week: pnpm",{"type":415,"tag":416,"props":2285,"children":2286},{},[2287,2289,2295,2297,2304,2306,2313],{"type":420,"value":2288},"I don't know which JavaScript package manager you are using but since I tried ",{"type":415,"tag":423,"props":2290,"children":2293},{"href":2291,"rel":2292},"https://pnpm.io/",[427],[2294],{"type":420,"value":362},{"type":420,"value":2296}," I don't want to use anything else because it's so fast! If you are interested to know why it's so fast and better than npm for instance, you can watch the talk ",{"type":415,"tag":423,"props":2298,"children":2301},{"href":2299,"rel":2300},"https://viteconf.org/2022/replay/pnpm",[427],[2302],{"type":420,"value":2303},"\"What makes pnpm performant\"",{"type":420,"value":2305}," that Zoltan Kochan gave at Vite Conf. Many ",{"type":415,"tag":423,"props":2307,"children":2310},{"href":2308,"rel":2309},"https://pnpm.io/workspaces#usage-examples",[427],[2311],{"type":420,"value":2312},"popular open-source projects",{"type":420,"value":2314}," like Vite and Vue are using pnpm.",{"type":415,"tag":416,"props":2316,"children":2317},{},[2318],{"type":420,"value":2319},"Here are some tips about pnpm:",{"type":415,"tag":637,"props":2321,"children":2322},{},[2323],{"type":415,"tag":497,"props":2324,"children":2325},{},[2326],{"type":420,"value":2327},"You can use pnpm to manage Node.js versions on your machine",{"type":415,"tag":416,"props":2329,"children":2330},{},[2331,2333,2338],{"type":420,"value":2332},"Previously, I was using ",{"type":415,"tag":423,"props":2334,"children":2336},{"href":1931,"rel":2335},[427],[2337],{"type":420,"value":1935},{"type":420,"value":2339}," to manage multiple installation of Node.js on my laptop and it worked fine. Yet I can now do that directly using pnpm env command:",{"type":415,"tag":416,"props":2341,"children":2342},{},[2343],{"type":415,"tag":1322,"props":2344,"children":2348},{"alt":2345,"className":2346,"src":2347},"Output of the pnpm env command in a terminal.",[1326,1327],"/posts/images/w462022tips_pnpm_env.png",[],{"type":415,"tag":637,"props":2350,"children":2351},{"start":796},[2352],{"type":415,"tag":497,"props":2353,"children":2354},{},[2355],{"type":420,"value":2356},"You can configure vscode to run npm scripts using pnpm",{"type":415,"tag":416,"props":2358,"children":2359},{},[2360],{"type":420,"value":2361},"A lot of the people I know don't use the scripts explorer of vscode to run the scripts contained in the package.json file of the project opened in vscode. It's a pity because it is an handy feature. And you can configure it in your settings to run scripts using a specific package manager, pnpm in my case.",{"type":415,"tag":416,"props":2363,"children":2364},{},[2365],{"type":415,"tag":1322,"props":2366,"children":2371},{"alt":2367,"className":2368,"src":2369,"width":2370},"Npm scripts view in vscode editor.",[1326,1327],"/posts/images/w462022tips_pnpm_scripts.png",600,[],{"type":415,"tag":637,"props":2373,"children":2374},{"start":840},[2375],{"type":415,"tag":497,"props":2376,"children":2377},{},[2378],{"type":420,"value":2379},"With pnpm, you can use aliases for packages you install",{"type":415,"tag":416,"props":2381,"children":2382},{},[2383,2385,2392],{"type":420,"value":2384},"Check the ",{"type":415,"tag":423,"props":2386,"children":2389},{"href":2387,"rel":2388},"https://pnpm.io/aliases",[427],[2390],{"type":420,"value":2391},"documentation",{"type":420,"value":2393}," to see why and how to use this feature.",{"type":415,"tag":443,"props":2395,"children":2397},{"id":2396},"the-gitlensazure-devops-tip-you-did-not-know-about-autolinks",[2398],{"type":420,"value":2399},"The GitLens/Azure DevOps tip you did not know about: autolinks",{"type":415,"tag":416,"props":2401,"children":2402},{},[2403],{"type":420,"value":2404},"GitLens, the awesome extension for vscode has a nice feature called \"autolinks\" that can make external references in your commit messages clickable links.",{"type":415,"tag":416,"props":2406,"children":2407},{},[2408],{"type":415,"tag":1322,"props":2409,"children":2414},{"alt":2410,"className":2411,"src":2412,"width":2413},"Autolinks GitLens settings view in vscode.",[1326,1327],"/posts/images/w462022tips_gitlens_autolink_1.png",1000,[],{"type":415,"tag":416,"props":2416,"children":2417},{},[2418,2420,2427],{"type":420,"value":2419},"If you are using Azure DevOps, this feature can become very handy for you commit messages that contain references to work items (usually an hasjtag followed by the work item number). You just have to configure # as the prefix and ",{"type":415,"tag":423,"props":2421,"children":2424},{"href":2422,"rel":2423},"https://dev.azure.com/%7BorganizationName%7D/%7BprojectName%7D/_workitems/edit/",[427],[2425],{"type":420,"value":2426},"https://dev.azure.com/{organizationName}/{projectName}/_workitems/edit/",{"type":415,"tag":2428,"props":2429,"children":2430},"num",{},[2431],{"type":420,"value":2432}," as the URL) to make it work.",{"type":415,"tag":416,"props":2434,"children":2435},{},[2436],{"type":415,"tag":1322,"props":2437,"children":2441},{"alt":2438,"className":2439,"src":2440,"width":2413},"A commit GitLens popin in vscode with an Azure DevOps link.",[1326,1327],"/posts/images/w462022tips_gitlens_autolink_2.png",[],{"type":415,"tag":416,"props":2443,"children":2444},{},[2445],{"type":420,"value":2446},"And that's it for this week, happy learning!",{"type":415,"tag":1420,"props":2448,"children":2449},{},[2450],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":2452},[2453,2454,2455],{"id":2216,"depth":796,"text":2219},{"id":2280,"depth":796,"text":2283},{"id":2396,"depth":796,"text":2399},"content:1.posts:44.w46-2022-tips-learned-this-week.md","1.posts/44.w46-2022-tips-learned-this-week.md",{"_path":136,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":135,"description":2459,"lead":2460,"date":2461,"image":2462,"badge":2464,"tags":2465,"body":2466,"_type":1435,"_id":3110,"_source":1437,"_file":3111,"_extension":1439},"This article is a discussion about API clients. Without being a comparison between the best API clients, this article talks about the pros and cons of some popular tools to send HTTP requests to an API. The goal is not to elect the best one, but rather to try to answer the following question: what we should consider when choosing an API client, and what are the challenges when using one?","What should you consider when choosing an API client?","2022-09-08T00:00:00.000Z",{"src":2463},"/images/tools_1.jpg",{"label":408},[206,213,360,210],{"type":412,"children":2467,"toc":3092},[2468,2472,2478,2492,2564,2592,2597,2603,2608,2617,2623,2628,2633,2642,2647,2665,2670,2676,2681,2694,2700,2705,2710,2731,2740,2760,2803,2809,2814,2823,2829,2851,2856,2862,2867,2872,2880,2886,2891,2896,2914,2923,2935,2940,2946,2952,2957,2962,2967,2972,2977,2983,2996,3001,3014,3042,3056,3064,3081,3087],{"type":415,"tag":416,"props":2469,"children":2470},{},[2471],{"type":420,"value":2459},{"type":415,"tag":443,"props":2473,"children":2475},{"id":2474},"some-context",[2476],{"type":420,"value":2477},"Some context",{"type":415,"tag":416,"props":2479,"children":2480},{},[2481,2483,2490],{"type":420,"value":2482},"I like discussing tooling because as a developer choosing the right tool is often what makes me more productive in my job. I am talking about the \"right tool\" because I am not necessarily looking for the best one but the most appropriate one for my needs in a given context. The topic of API clients is not new to me, indeed the first post I wrote on this blog in March 2019 (and my most-read article on DEV.to where I re-posted it) was about using the vscode extension \"REST Client\" instead of Postman. ",{"type":415,"tag":423,"props":2484,"children":2487},{"href":2485,"rel":2486},"https://www.techwatching.dev/posts/testing-your-api-with-rest-client",[427],[2488],{"type":420,"value":2489},"This article",{"type":420,"value":2491}," is still relevant and in fact, I have been using REST Client as my main API client for a few years on different projects and in different teams.",{"type":415,"tag":416,"props":2493,"children":2494},{},[2495,2497,2504,2506,2513,2514,2521,2522,2529,2530,2537,2538,2545,2546,2553,2555,2562],{"type":420,"value":2496},"Yet I am not writing now to convince you to use \"REST Client\" or any other tool. Besides, you probably already have a favorite tool you are using to send HTTP requests. Indeed, there are many options: ",{"type":415,"tag":423,"props":2498,"children":2501},{"href":2499,"rel":2500},"https://www.postman.com",[427],[2502],{"type":420,"value":2503},"Postman",{"type":420,"value":2505},", ",{"type":415,"tag":423,"props":2507,"children":2510},{"href":2508,"rel":2509},"https://marketplace.visualstudio.com/items?itemName=humao.rest-client",[427],[2511],{"type":420,"value":2512},"REST Client",{"type":420,"value":2505},{"type":415,"tag":423,"props":2515,"children":2518},{"href":2516,"rel":2517},"https://www.thunderclient.com/",[427],[2519],{"type":420,"value":2520},"Thunder Client",{"type":420,"value":2505},{"type":415,"tag":423,"props":2523,"children":2526},{"href":2524,"rel":2525},"https://nightingale.rest/",[427],[2527],{"type":420,"value":2528},"Nightingale",{"type":420,"value":2505},{"type":415,"tag":423,"props":2531,"children":2534},{"href":2532,"rel":2533},"https://insomnia.rest/",[427],[2535],{"type":420,"value":2536},"Insomnia",{"type":420,"value":2505},{"type":415,"tag":423,"props":2539,"children":2542},{"href":2540,"rel":2541},"https://rapidapi.com/products/api-design/",[427],[2543],{"type":420,"value":2544},"RapidAPI Client",{"type":420,"value":2505},{"type":415,"tag":423,"props":2547,"children":2550},{"href":2548,"rel":2549},"https://hoppscotch.io/fr/",[427],[2551],{"type":420,"value":2552},"Hoppscotch",{"type":420,"value":2554},", and ",{"type":415,"tag":423,"props":2556,"children":2559},{"href":2557,"rel":2558},"https://httpie.io/",[427],[2560],{"type":420,"value":2561},"HTTPie",{"type":420,"value":2563}," just to name a few. There are many reasons why people choose one tool over another:",{"type":415,"tag":493,"props":2565,"children":2566},{},[2567,2572,2577,2582,2587],{"type":415,"tag":497,"props":2568,"children":2569},{},[2570],{"type":420,"value":2571},"it has more features than others",{"type":415,"tag":497,"props":2573,"children":2574},{},[2575],{"type":420,"value":2576},"some colleagues suggested it",{"type":415,"tag":497,"props":2578,"children":2579},{},[2580],{"type":420,"value":2581},"it's the latest tool featured on ProductHunt",{"type":415,"tag":497,"props":2583,"children":2584},{},[2585],{"type":420,"value":2586},"everyone in the company uses this API client",{"type":415,"tag":497,"props":2588,"children":2589},{},[2590],{"type":420,"value":2591},"it would be time-consuming to learn how to use another API Client",{"type":415,"tag":416,"props":2593,"children":2594},{},[2595],{"type":420,"value":2596},"I don't think these are good enough reasons, so I will talk to you about what matters to me when using an API Client.",{"type":415,"tag":443,"props":2598,"children":2600},{"id":2599},"ease-of-use",[2601],{"type":420,"value":2602},"Ease of use",{"type":415,"tag":416,"props":2604,"children":2605},{},[2606],{"type":420,"value":2607},"When I am using an API client, it's to make requests to an API and get responses. It's often to test an API or to debug the API I am developing. Therefore what's important to me is to have a simple tool that makes it easy to write HTTP requests, and display the responses and that's it.",{"type":415,"tag":416,"props":2609,"children":2610},{},[2611],{"type":415,"tag":1322,"props":2612,"children":2616},{"alt":2613,"className":2614,"src":2615},"A scrabble tile that says keep things simple.",[1326,1327],"/posts/images/httpclients_1.jpg",[],{"type":415,"tag":616,"props":2618,"children":2620},{"id":2619},"all-conceivable-functionalities-vs-essentials-features",[2621],{"type":420,"value":2622},"All conceivable functionalities vs. essentials features",{"type":415,"tag":416,"props":2624,"children":2625},{},[2626],{"type":420,"value":2627},"A lot of tools try to compete with each other by providing more features or very advanced features. First, it's useless because you are not going to use most of them. Second, it's counterproductive because having too many features will make an API client more complicated to use.",{"type":415,"tag":416,"props":2629,"children":2630},{},[2631],{"type":420,"value":2632},"One good example is Postman which went from a simple tool that makes HTTP requests to a real white elephant. To be honest, I am quite impressed with all the features you can find in Postman: the company has done an amazing job to build a platform that helps you in each step of your API lifecycle (specifications, design, documentation, testing, monitoring, ...). However, the downside is that Postman became bigger, slower, and less easy to use. I have no doubt Postman brings a lot of value to many developers that are building their APIs, yet a lot of people (like me) don't need all these unnecessary features and would be better with a simpler tool. Postman is just an example, there are many other API clients that overwhelm you with concepts you don't need (workspace, collections, mocks, flows), that require you to sign in and configure a bunch of things before being able to write an HTTP request.",{"type":415,"tag":416,"props":2634,"children":2635},{},[2636],{"type":415,"tag":1322,"props":2637,"children":2641},{"alt":2638,"className":2639,"src":2640},"Logo of Postman.",[1326,1327],"/posts/images/httpclients_2.jpg",[],{"type":415,"tag":416,"props":2643,"children":2644},{},[2645],{"type":420,"value":2646},"So what are the essentials features an API Client should have besides sending requests to an API and displaying the response?",{"type":415,"tag":493,"props":2648,"children":2649},{},[2650,2655,2660],{"type":415,"tag":497,"props":2651,"children":2652},{},[2653],{"type":420,"value":2654},"First, it should support the \"protocol\" (REST, SOAP, GraphQL, gRPC) used by the API you want to query (pretty obvious but not all API clients support gRPC or GraphQL for instance).",{"type":415,"tag":497,"props":2656,"children":2657},{},[2658],{"type":420,"value":2659},"Second, it should support environment variables. You will need them to easily switch between environments: sending requests to an API in QA and to the same API in Production for instance. Variables can also be useful to store the result of a previous request and use it in another request.",{"type":415,"tag":497,"props":2661,"children":2662},{},[2663],{"type":420,"value":2664},"Third, it should allow version control of your request (we will come back to this later).",{"type":415,"tag":416,"props":2666,"children":2667},{},[2668],{"type":420,"value":2669},"I think that's all you need. In my opinion, testing and integration with CI/CD pipelines (to automate testing) are nice-to-have functionalities but not mandatory. Indeed, if you want to do some complex tests with big scenarios that run in parallel, you are probably not using an API Client for that and rather develop proper integration tests using more appropriate tools.",{"type":415,"tag":616,"props":2671,"children":2673},{"id":2672},"integrated-tools-vs-standalone-tools",[2674],{"type":420,"value":2675},"Integrated tools vs. standalone tools",{"type":415,"tag":416,"props":2677,"children":2678},{},[2679],{"type":420,"value":2680},"Some API Clients are standalone software, whether rich clients (like Postman, or Nightingale) or web applications (like Hoppscotch). Some are directly integrated with your developer tools and IDEs. I feel more productive when everything is in one place and I leave my IDE as little as possible, so that's why I prefer an API Client integrated with my IDE. I find it easier to use a familiar UI in a tool I already know and like rather than using a completely different tool. Everyone has his own way of working so you may prefer a standalone tool and that's fine as well. Just take the time to think about what's best for you.",{"type":415,"tag":416,"props":2682,"children":2683},{},[2684,2686,2692],{"type":420,"value":2685},"If you are fond of vscode, you have different API clients available as extensions, for instance: REST Client, Thunder Client, and RapidAPI Client for VS Code. If your everyday IDE is Visual Studio, there is an extension ",{"type":415,"tag":423,"props":2687,"children":2690},{"href":2688,"rel":2689},"https://marketplace.visualstudio.com/items?itemName=MadsKristensen.RestClient",[427],[2691],{"type":420,"value":2512},{"type":420,"value":2693}," based on the REST Client for vscode. If you only swear by IntelliJ IDEA or Rider, all JetBrains IDEs have a built-in HTTP Client.",{"type":415,"tag":616,"props":2695,"children":2697},{"id":2696},"gui-tools-vs-text-based-tools",[2698],{"type":420,"value":2699},"GUI tools vs. text-based tools",{"type":415,"tag":416,"props":2701,"children":2702},{},[2703],{"type":420,"value":2704},"There are 2 types of tools: GUI tools and text-based tools.",{"type":415,"tag":416,"props":2706,"children":2707},{},[2708],{"type":420,"value":2709},"Generally, GUI tools are easier to use than text-based tools because they offer a simple way to create and send an HTTP request. Of course, this is not always the case: I already talked about some API clients that have a complex UI because of their many features. Fortunately, other GUI tools have managed to keep a simple and clean user interface. That is precisely how Thunder Client has been designed: a lightweight API client with an easy-to-use UI; and it's a success. I particularly like the GUI-based tests of Thunder Client that allow you to quickly do basic tests on HTTP responses without scripting anything. Test scenarios will be quite limited, yet you probably don't want to use an API client for more complex scenarios. There are many other API clients with a great UI, take the time to test several, the choice will probably be a matter of personal preference.",{"type":415,"tag":416,"props":2711,"children":2712},{},[2713,2715,2720,2722,2729],{"type":420,"value":2714},"Then, why bother with a text-based tool instead? How could it be easier to manually write an HTTP request in a plain text file than using a GUI for that? Because when you use a text-based API client like ",{"type":415,"tag":550,"props":2716,"children":2718},{"className":2717},[],[2719],{"type":420,"value":2512},{"type":420,"value":2721}," you write your request following the standard ",{"type":415,"tag":423,"props":2723,"children":2726},{"href":2724,"rel":2725},"https://www.rfc-editor.org/rfc/rfc7230#section-3",[427],[2727],{"type":420,"value":2728},"RFC 2730",{"type":420,"value":2730},". It's a standard so you will find it everywhere to describe the requests, including in the documentation of most popular APIs. It's quite straightforward so you won't have a hard time writing the requests.",{"type":415,"tag":416,"props":2732,"children":2733},{},[2734],{"type":415,"tag":1322,"props":2735,"children":2739},{"alt":2736,"className":2737,"src":2738},"REST Client in vscode.",[1326,1327],"/posts/images/httpclients_3.png",[],{"type":415,"tag":416,"props":2741,"children":2742},{},[2743,2745,2751,2753,2758],{"type":420,"value":2744},"Moreover, these kinds of API clients (which are often IDEs extensions or built-in in an IDE) will understand the HTTP text files (with ",{"type":415,"tag":2746,"props":2747,"children":2748},"em",{},[2749],{"type":420,"value":2750},".rest",{"type":420,"value":2752}," or ",{"type":415,"tag":2746,"props":2754,"children":2755},{},[2756],{"type":420,"value":2757},".http",{"type":420,"value":2759}," extension) and help you write the requests. You can write multiple requests in the same file and don't have to learn how to use a specific tool, just to write requests in plain text. Therefore, once you are used to the raw syntax of HTTP requests, using a text-based tool becomes even easier to use than the simplest GUI tool.",{"type":415,"tag":588,"props":2761,"children":2762},{"icon":728},[2763,2768],{"type":415,"tag":416,"props":2764,"children":2765},{},[2766],{"type":420,"value":2767},"I already mentioned them before but examples of such text-based tools are:",{"type":415,"tag":493,"props":2769,"children":2770},{},[2771,2781,2791],{"type":415,"tag":497,"props":2772,"children":2773},{},[2774,2779],{"type":415,"tag":423,"props":2775,"children":2777},{"href":2508,"rel":2776},[427],[2778],{"type":420,"value":2512},{"type":420,"value":2780}," in vscode",{"type":415,"tag":497,"props":2782,"children":2783},{},[2784,2789],{"type":415,"tag":423,"props":2785,"children":2787},{"href":2688,"rel":2786},[427],[2788],{"type":420,"value":2512},{"type":420,"value":2790}," in Visual Studio",{"type":415,"tag":497,"props":2792,"children":2793},{},[2794,2801],{"type":415,"tag":423,"props":2795,"children":2798},{"href":2796,"rel":2797},"https://www.jetbrains.com/help/rider/Http_client_in__product__code_editor.html",[427],[2799],{"type":420,"value":2800},"HTTP Client",{"type":420,"value":2802}," in Rider or any other JetBrains IDE",{"type":415,"tag":443,"props":2804,"children":2806},{"id":2805},"collaboration",[2807],{"type":420,"value":2808},"Collaboration",{"type":415,"tag":416,"props":2810,"children":2811},{},[2812],{"type":420,"value":2813},"The reason why most API clients are a no go for me is that they don't facilitate collaboration. Collaboration is an important part of my job so I need an API client that makes it easy to share requests with others.",{"type":415,"tag":416,"props":2815,"children":2816},{},[2817],{"type":415,"tag":1322,"props":2818,"children":2822},{"alt":2819,"className":2820,"src":2821},"A scrabble board with words spelling teamwork.",[1326,1327],"/posts/images/httpclients_4.jpg",[],{"type":415,"tag":616,"props":2824,"children":2826},{"id":2825},"are-you-ready-to-pay-the-price-for-team-collaboration",[2827],{"type":420,"value":2828},"Are you ready to pay the price for team collaboration?",{"type":415,"tag":416,"props":2830,"children":2831},{},[2832,2834,2840,2842,2849],{"type":420,"value":2833},"Although a few API clients totally lack collaboration features, many of them have nice features to collaborate with your team. However, that is also how the companies behind these tools make money: charging for collaboration features. Usually working with an API client is free for individuals but you have to pay when you are a company or a team. You can see for instance the pricing page of ",{"type":415,"tag":423,"props":2835,"children":2838},{"href":2836,"rel":2837},"https://www.postman.com/pricing/",[427],[2839],{"type":420,"value":2503},{"type":420,"value":2841}," or the one from ",{"type":415,"tag":423,"props":2843,"children":2846},{"href":2844,"rel":2845},"https://rapidapi.com/products/api-design",[427],[2847],{"type":420,"value":2848},"RapidAPI",{"type":420,"value":2850},". I have nothing against that: these companies put a lot of work and money into making these great tools, they need to be profitable and make money. Yet the question is: are you (or is your company) willing to pay for an API client?",{"type":415,"tag":416,"props":2852,"children":2853},{},[2854],{"type":420,"value":2855},"If the answer is yes, then fine, choose the best tool for your team, buy some licenses and enjoy. If the answer is no, then how do you intend to work with your colleagues without these collaboration features? Let me guess, you have found a way to export your collection of requests to a file. So each time a new colleague joins your team, you send him/her the file by mail; no one has the same version of the file but you don't care. Or maybe you have put it in a shared drive so everyone can edit the requests; you just have to hope that 2 people don't edit it at the same time. Why not put the file on a flash drive and pass it on to each other while you're at it? I think you understood what I was getting at: if you work with a team, pay for collaboration features, or choose a free API client suitable for teamwork.",{"type":415,"tag":616,"props":2857,"children":2859},{"id":2858},"version-control-is-the-key",[2860],{"type":420,"value":2861},"Version control is the key",{"type":415,"tag":416,"props":2863,"children":2864},{},[2865],{"type":420,"value":2866},"In my daily work as a developer, I work in a team and I want to share requests with them, allow them to edit them, and see the history of changes made. It's funny because it's the same kind of thing you want for your application code when developing. And what helps you with that? Version control. So if you use a git repository to version your application code, there is no reason why it would not be a good fit for your collection of HTTP requests.",{"type":415,"tag":416,"props":2868,"children":2869},{},[2870],{"type":420,"value":2871},"Besides, that is exactly what some API clients like Thunder Client or Postman offer: integration with external git repositories. Please note that is not necessary for your API client to have a git integration to share your requests through git, as long as your requests are available in a text file you can version it's fine. So why bother with an obscure \"cloud synchronization\" of your requests when you can share them with your team through a git repository?",{"type":415,"tag":588,"props":2873,"children":2874},{"icon":590},[2875],{"type":415,"tag":416,"props":2876,"children":2877},{},[2878],{"type":420,"value":2879},"The sweet spot when you are building an API is to store your API application code and your requests to call/test this API in the same git repository.",{"type":415,"tag":616,"props":2881,"children":2883},{"id":2882},"how-requests-are-stored-is-important",[2884],{"type":420,"value":2885},"How requests are stored is important",{"type":415,"tag":416,"props":2887,"children":2888},{},[2889],{"type":420,"value":2890},"I previously mentioned how some tools like Thunder Client offer a git integration with external git repositories. Requests and environment variables used by Thunder Client are stored in JSON files that you can add to your git repository. In a similar way, with Postman you can export requests and environment variables in JSON files and version them. There is a big problem with this way of doing things: using JSON files to version HTTP requests is not convenient at all! I have no problem with the JSON format itself, as a developer, it's something I often use. However, HTTP requests stored in a JSON file are not easily readable by a human. If you version HTTP requests, it is not to struggle to understand the history of changes or to have a hard time resolving conflicts between changes made by you and other developers of the team.",{"type":415,"tag":416,"props":2892,"children":2893},{},[2894],{"type":420,"value":2895},"I have seen some QA people using Postman for integration testing that were exporting their Postman data (collection, environments, ...) and versioning it in a git repository. What started out as a good intention turned out to be a very bad idea:",{"type":415,"tag":493,"props":2897,"children":2898},{},[2899,2904,2909],{"type":415,"tag":497,"props":2900,"children":2901},{},[2902],{"type":420,"value":2903},"the exported collection was just a huge JSON file that contained all the requests and tests, so each change was made to this same file with the inconveniences I mentioned previously",{"type":415,"tag":497,"props":2905,"children":2906},{},[2907],{"type":420,"value":2908},"editing the JSON file directly and especially the tests written in javascript is a nightmare because with the Postman export the code is stored as string in the JSON (forget about using code quality tools 🥲)",{"type":415,"tag":497,"props":2910,"children":2911},{},[2912],{"type":420,"value":2913},"the environment files versioned were containing secrets so secrets were committed in clear text in the git repository 😱",{"type":415,"tag":416,"props":2915,"children":2916},{},[2917],{"type":415,"tag":1322,"props":2918,"children":2922},{"alt":2919,"className":2920,"src":2921},"A silhouette of a man.",[1326,1327],"/posts/images/httpclients_5.jpg",[],{"type":415,"tag":416,"props":2924,"children":2925},{},[2926,2928,2933],{"type":420,"value":2927},"To avoid these caveats, I think the best way to store HTTP requests is to store them in HTTP text files following the standard ",{"type":415,"tag":423,"props":2929,"children":2931},{"href":2724,"rel":2930},[427],[2932],{"type":420,"value":2728},{"type":420,"value":2934},". I already talked about text-based tools that were following this approach, unfortunately, I did not find any GUI tool that was doing that (probably because GUI tools always have extra features with things that can't be easily stored in an HTTP file).",{"type":415,"tag":416,"props":2936,"children":2937},{},[2938],{"type":420,"value":2939},"If you pay for an API client with which your requests are synchronized in the cloud with your colleagues, you probably don't care how the requests are stored. However, I hope your API client knows how to handle merge and conflicts (1 or more requests edited by several colleagues at the same time for example). Even if it does, be aware it can be not that easy to manually resolve these conflicts in a GUI tool.",{"type":415,"tag":443,"props":2941,"children":2943},{"id":2942},"final-thoughts",[2944],{"type":420,"value":2945},"Final thoughts",{"type":415,"tag":616,"props":2947,"children":2949},{"id":2948},"using-the-right-tool-for-a-given-context",[2950],{"type":420,"value":2951},"Using the right tool for a given context",{"type":415,"tag":416,"props":2953,"children":2954},{},[2955],{"type":420,"value":2956},"I think you understood that there was no perfect API client. So once again, the answer to choosing the right one will be \"it depends\".",{"type":415,"tag":416,"props":2958,"children":2959},{},[2960],{"type":420,"value":2961},"If for your project, you need to choose an API client that will only be used by developers of your team that are coding in vscode, maybe REST Client, Thunder Client, or RapidAPI Client for VS Code are interesting choices.",{"type":415,"tag":416,"props":2963,"children":2964},{},[2965],{"type":420,"value":2966},"For a lot of reasons I explained previously I am not a big fan of big standalone tools like Postman. Yet, Postman is installed on my laptop, and if people send me their requests collection to test their API, I would be crazy not to take advantage of it and rewrite every request just because I prefer to use other tools.",{"type":415,"tag":416,"props":2968,"children":2969},{},[2970],{"type":420,"value":2971},"I like the HTTP request format and I think people using API clients should be familiar with it. Unfortunately, sometimes people that need to use an API client don't have a technical background. In that case, maybe GUI tools with a simple interface are better suited than a text-based tool. Thunder Client with its scriptless testing feature might be a good option (although it requires you to install vscode which is more intended for developers).",{"type":415,"tag":416,"props":2973,"children":2974},{},[2975],{"type":420,"value":2976},"There are many API clients that you can use. Without knowing all of them, it can be a good idea to know what are the alternatives to the most famous ones, in order to choose the right one for your current context.",{"type":415,"tag":616,"props":2978,"children":2980},{"id":2979},"what-am-i-currently-using",[2981],{"type":420,"value":2982},"What am I currently using?",{"type":415,"tag":416,"props":2984,"children":2985},{},[2986,2988,2994],{"type":420,"value":2987},"As far as I'm concerned, I prefer using HTTP requests in HTTP files that I edit in a text editor or an IDE and that I commit to a git repository. That way, I have several HTTP files with HTTP requests grouped by concern. When I or one of my colleagues make a change to a request, the others just have to do a ",{"type":415,"tag":550,"props":2989,"children":2991},{"className":2990},[],[2992],{"type":420,"value":2993},"git pull",{"type":420,"value":2995}," to get the change. We use something standard and we are not directly dependent on a specific tool because HTTP files are supported by several tools.",{"type":415,"tag":416,"props":2997,"children":2998},{},[2999],{"type":420,"value":3000},"I have used REST Client for quite a while now, and I am happy with it: simple but works fine, no need to pay a subscription, it's just a vscode extension to install.",{"type":415,"tag":416,"props":3002,"children":3003},{},[3004,3006,3012],{"type":420,"value":3005},"However, as I have started recently to use more and more Rider, I am also using more and more its built-in HTTP Client which is awesome. If you are using Rider or one of the other JetBrains IDEs I recommend you to try the ",{"type":415,"tag":423,"props":3007,"children":3009},{"href":2796,"rel":3008},[427],[3010],{"type":420,"value":3011},"built-in HTTP Client",{"type":420,"value":3013}," because:",{"type":415,"tag":493,"props":3015,"children":3016},{},[3017,3022,3027,3032,3037],{"type":415,"tag":497,"props":3018,"children":3019},{},[3020],{"type":420,"value":3021},"you don't have to leave your favorite IDE to make HTTP requests to an API",{"type":415,"tag":497,"props":3023,"children":3024},{},[3025],{"type":420,"value":3026},"it supports HTTP requests, gRPC requests, WebSocket requests, GraphQL",{"type":415,"tag":497,"props":3028,"children":3029},{},[3030],{"type":420,"value":3031},"it has many features to help you write the requests like converting cURL requests to the HTTP request format",{"type":415,"tag":497,"props":3033,"children":3034},{},[3035],{"type":420,"value":3036},"it supports environment variables that can be stored in a public environment file (stored in the git repository) or private environment file (local file containing sensitive information that will not be committed to the repository)",{"type":415,"tag":497,"props":3038,"children":3039},{},[3040],{"type":420,"value":3041},"it allows you to write response handler scripts in JavaScript (directly in the HTTP files or in external js files), which opens the door to many possibilities like writing tests",{"type":415,"tag":416,"props":3043,"children":3044},{},[3045,3047,3054],{"type":420,"value":3046},"And the icing on the cake, as you see on the tweet below there is a ",{"type":415,"tag":423,"props":3048,"children":3051},{"href":3049,"rel":3050},"https://github.com/restcli/restcli",[427],[3052],{"type":420,"value":3053},"CLI",{"type":420,"value":3055}," to execute these HTTP files from the command line. It means that if you have written tests for your HTTP requests with the HTTP Client, you can execute them in your CI/CD pipeline. It also means that even without a JetBrains' IDE you can execute HTTP request files.",{"type":415,"tag":1472,"props":3057,"children":3059},{"className":3058},[1475,1476],[3060],{"type":415,"tag":1479,"props":3061,"children":3063},{"id":3062},"1546831245274931201",[],{"type":415,"tag":588,"props":3065,"children":3066},{"icon":728},[3067],{"type":415,"tag":416,"props":3068,"children":3069},{},[3070,3072,3079],{"type":420,"value":3071},"Even if you are using a private environment file to store secrets environments variables, it's often not very convenient to share it with your news colleagues. I wrote ",{"type":415,"tag":423,"props":3073,"children":3076},{"href":3074,"rel":3075},"https://www.techwatching.dev/posts/http-clients-secrets",[427],[3077],{"type":420,"value":3078},"an article",{"type":420,"value":3080}," on the topic that showed how you can store secrets in Azure Key Vault and script their retrieval and the generation of the secret environment file using Azure CLI.",{"type":415,"tag":616,"props":3082,"children":3084},{"id":3083},"in-summary",[3085],{"type":420,"value":3086},"In summary",{"type":415,"tag":416,"props":3088,"children":3089},{},[3090],{"type":420,"value":3091},"I talked about a lot of things but in concrete terms what matters to me when using an API Client is ease of use and collaboration. You may not have exactly the same needs, yet I think these are 2 aspects you should pay attention to when choosing an API Client. As you have read in this article, each API client has its advantages and inconveniences so even if you favor simplicity and collaboration you won't find a perfect tool. So what should you choose? I don't have the answer to that, I think you should try different tools and find what is the most appropriate in your context. But you should definitely not take the first one that comes along just because others use it.",{"title":401,"searchDepth":796,"depth":796,"links":3093},[3094,3095,3100,3105],{"id":2474,"depth":796,"text":2477},{"id":2599,"depth":796,"text":2602,"children":3096},[3097,3098,3099],{"id":2619,"depth":840,"text":2622},{"id":2672,"depth":840,"text":2675},{"id":2696,"depth":840,"text":2699},{"id":2805,"depth":796,"text":2808,"children":3101},[3102,3103,3104],{"id":2825,"depth":840,"text":2828},{"id":2858,"depth":840,"text":2861},{"id":2882,"depth":840,"text":2885},{"id":2942,"depth":796,"text":2945,"children":3106},[3107,3108,3109],{"id":2948,"depth":840,"text":2951},{"id":2979,"depth":840,"text":2982},{"id":3083,"depth":840,"text":3086},"content:1.posts:43.http-clients.md","1.posts/43.http-clients.md",{"_path":133,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":132,"description":3113,"lead":3114,"date":3115,"badge":3116,"image":3117,"tags":3119,"body":3120,"_type":1435,"_id":4635,"_source":1437,"_file":4636,"_extension":1439},"In this article, I talked about my latest project: how I built a script to automate the setup of my developer machine using Boxstarter, Chocolatey, Winget, and PowerShell... and how I learned a few things along the way.","A git repository, 2 packages managers, a little bit of scripting, and here is my new environment ready","2022-09-02T00:00:00.000Z",{"label":408},{"src":3118},"/images/laptop_2.jpg",[291,249,293,296,206],{"type":412,"children":3121,"toc":4622},[3122,3126,3130,3135,3140,3149,3154,3177,3183,3188,3211,3243,3257,3286,3295,3303,3323,3328,3451,3463,3469,3475,3496,3501,3516,3522,3527,3536,3541,3546,3629,3637,3642,3656,3670,3676,3696,3734,3739,4004,4026,4031,4079,4087,4093,4098,4285,4305,4311,4316,4325,4330,4380,4392,4469,4474,4479,4485,4490,4512,4526,4531,4558,4572,4578,4590,4595,4600,4613,4618],{"type":415,"tag":416,"props":3123,"children":3124},{},[3125],{"type":420,"value":3113},{"type":415,"tag":443,"props":3127,"children":3128},{"id":445},[3129],{"type":420,"value":448},{"type":415,"tag":416,"props":3131,"children":3132},{},[3133],{"type":420,"value":3134},"You have probably already faced this situation when you get a new personal or professional laptop, and you have to reinstall all the software you are using daily on this machine. Usually, you lose a few hours to a day to set up your developer environment and not everything is exactly as it was on your previous laptop because you always forget some things.",{"type":415,"tag":416,"props":3136,"children":3137},{},[3138],{"type":420,"value":3139},"It's not even only about installing software but also configuring your environment to meet your habits. And that's important because a properly configured developer environment with the right tools is what makes you productive in your everyday developer life.",{"type":415,"tag":416,"props":3141,"children":3142},{},[3143],{"type":415,"tag":1322,"props":3144,"children":3148},{"alt":3145,"className":3146,"src":3147},"A collection of office supplies laid out on a table.",[1326,1327],"/posts/images/automate_developer_machine_tool.jpg",[],{"type":415,"tag":416,"props":3150,"children":3151},{},[3152],{"type":420,"value":3153},"A lot of people have solved this issue by scripting their developer environment setup, and I decided it was time for me to do the same. And believe me, it was not for the beauty of having everything automatically installed but to stop wasting my time each time I change or reinstall my laptop.",{"type":415,"tag":416,"props":3155,"children":3156},{},[3157,3159,3166,3168,3175],{"type":420,"value":3158},"Please note that what I did to setup my developer machine was inspired by the ",{"type":415,"tag":423,"props":3160,"children":3163},{"href":3161,"rel":3162},"https://github.com/Microsoft/windows-dev-box-setup-scripts",[427],[3164],{"type":420,"value":3165},"Windows Dev Box setup scripts",{"type":420,"value":3167}," GitHub repository and other repositories using boxstarter (check ",{"type":415,"tag":423,"props":3169,"children":3172},{"href":3170,"rel":3171},"https://github.com/laurentkempe/Cacao",[427],[3173],{"type":420,"value":3174},"Laurent Kempé's repository",{"type":420,"value":3176}," for instance) to install their Windows environment.",{"type":415,"tag":443,"props":3178,"children":3180},{"id":3179},"how-does-it-work",[3181],{"type":420,"value":3182},"How does it work?",{"type":415,"tag":416,"props":3184,"children":3185},{},[3186],{"type":420,"value":3187},"There are many ways to automate the setup of a developer machine. Before choosing one, I had a few requirements/preferences:",{"type":415,"tag":493,"props":3189,"children":3190},{},[3191,3196,3201,3206],{"type":415,"tag":497,"props":3192,"children":3193},{},[3194],{"type":420,"value":3195},"installation of my machine should be simple to script and run",{"type":415,"tag":497,"props":3197,"children":3198},{},[3199],{"type":420,"value":3200},"no need to pre-install some software before launching the installation of my environment",{"type":415,"tag":497,"props":3202,"children":3203},{},[3204],{"type":420,"value":3205},"avoid manual steps or user interaction during the installation",{"type":415,"tag":497,"props":3207,"children":3208},{},[3209],{"type":420,"value":3210},"be able easily to share some software configurations between laptops",{"type":415,"tag":416,"props":3212,"children":3213},{},[3214,3216,3223,3225,3232,3234,3241],{"type":420,"value":3215},"Because of these requirements, I chose to use ",{"type":415,"tag":423,"props":3217,"children":3220},{"href":3218,"rel":3219},"https://boxstarter.org/",[427],[3221],{"type":420,"value":3222},"Boxstarter",{"type":420,"value":3224},". It has several interesting features (check the ",{"type":415,"tag":423,"props":3226,"children":3229},{"href":3227,"rel":3228},"https://boxstarter.org/whyboxstarter",[427],[3230],{"type":420,"value":3231},"website",{"type":420,"value":3233},") but the one I like the most is you can launch your installation process directly by ",{"type":415,"tag":423,"props":3235,"children":3238},{"href":3236,"rel":3237},"https://boxstarter.org/weblauncher",[427],[3239],{"type":420,"value":3240},"clicking on a link",{"type":420,"value":3242},". You just need an URL like this one:",{"type":415,"tag":773,"props":3244,"children":3246},{"className":1345,"code":3245,"language":212,"meta":401,"style":401},"https://boxstarter.org/package/nr/url?{urlToYourInstallationScript}\n",[3247],{"type":415,"tag":550,"props":3248,"children":3249},{"__ignoreMap":401},[3250],{"type":415,"tag":783,"props":3251,"children":3252},{"class":785,"line":786},[3253],{"type":415,"tag":783,"props":3254,"children":3255},{"style":1362},[3256],{"type":420,"value":3245},{"type":415,"tag":416,"props":3258,"children":3259},{},[3260,3262,3268,3270,3277,3279,3285],{"type":420,"value":3261},"The ",{"type":415,"tag":550,"props":3263,"children":3265},{"className":3264},[],[3266],{"type":420,"value":3267},"{urlToYourInstallationScript}",{"type":420,"value":3269}," part is the URL where is stored your installation script. I put mine in a ",{"type":415,"tag":423,"props":3271,"children":3274},{"href":3272,"rel":3273},"https://github.com/TechWatching/dotfiles/blob/main/boxstarter.ps1",[427],[3275],{"type":420,"value":3276},"public GitHub repository",{"type":420,"value":3278}," called ",{"type":415,"tag":550,"props":3280,"children":3282},{"className":3281},[],[3283],{"type":420,"value":3284},"dotfiles",{"type":420,"value":755},{"type":415,"tag":416,"props":3287,"children":3288},{},[3289],{"type":415,"tag":1322,"props":3290,"children":3294},{"alt":3291,"className":3292,"src":3293},"Boxstarter website.",[1326,1327],"/posts/images/automate_developer_machine_boxstarter.png",[],{"type":415,"tag":588,"props":3296,"children":3297},{"icon":590},[3298],{"type":415,"tag":416,"props":3299,"children":3300},{},[3301],{"type":420,"value":3302},"Boxstarter was a nice discovery, I barely scratched the surface but it can do interesting things like remote installations.",{"type":415,"tag":416,"props":3304,"children":3305},{},[3306,3308,3314,3316,3321],{"type":420,"value":3307},"In this repository, I also put different scripts (to separate the different steps instead of having one big installation script file), some config files I wanted to use in my setup (my ",{"type":415,"tag":550,"props":3309,"children":3311},{"className":3310},[],[3312],{"type":420,"value":3313},".gitconfig",{"type":420,"value":3315}," file for instance), and a readme with some explanations and the link to launch the installation. I did not reinvent the wheel as it's more or less how the ",{"type":415,"tag":423,"props":3317,"children":3319},{"href":3161,"rel":3318},[427],[3320],{"type":420,"value":3165},{"type":420,"value":3322}," work.",{"type":415,"tag":416,"props":3324,"children":3325},{},[3326],{"type":420,"value":3327},"Because I wanted to share some software configurations between laptops (or between installations), using a GitHub repository is interesting. The first thing my setup script does is to install Git, then it clones this repository to have access to the other scripts and configuration files.",{"type":415,"tag":773,"props":3329,"children":3331},{"className":2236,"code":3330,"language":248,"meta":401,"style":401},"# Install git and clone repository containing scripts and config files\nchoco install -y git --params \"/GitOnlyOnPath /NoShellIntegration /WindowsTerminal\"\nRefreshEnv\ngit clone https://github.com/TechWatching/dotfiles.git \"$env:USERPROFILE\\dotfiles\"\n",[3332],{"type":415,"tag":550,"props":3333,"children":3334},{"__ignoreMap":401},[3335,3344,3386,3394],{"type":415,"tag":783,"props":3336,"children":3337},{"class":785,"line":786},[3338],{"type":415,"tag":783,"props":3339,"children":3341},{"style":3340},"--shiki-light:#90A4AE;--shiki-default:#546E7A;--shiki-dark:#676E95;--shiki-light-font-style:italic;--shiki-default-font-style:italic;--shiki-dark-font-style:italic",[3342],{"type":420,"value":3343},"# Install git and clone repository containing scripts and config files\n",{"type":415,"tag":783,"props":3345,"children":3346},{"class":785,"line":796},[3347,3352,3357,3362,3367,3372,3376,3381],{"type":415,"tag":783,"props":3348,"children":3349},{"style":1362},[3350],{"type":420,"value":3351},"choco install ",{"type":415,"tag":783,"props":3353,"children":3354},{"style":790},[3355],{"type":420,"value":3356},"-",{"type":415,"tag":783,"props":3358,"children":3359},{"style":1362},[3360],{"type":420,"value":3361},"y git ",{"type":415,"tag":783,"props":3363,"children":3364},{"style":790},[3365],{"type":420,"value":3366},"--",{"type":415,"tag":783,"props":3368,"children":3369},{"style":1362},[3370],{"type":420,"value":3371},"params ",{"type":415,"tag":783,"props":3373,"children":3374},{"style":790},[3375],{"type":420,"value":813},{"type":415,"tag":783,"props":3377,"children":3378},{"style":825},[3379],{"type":420,"value":3380},"/GitOnlyOnPath /NoShellIntegration /WindowsTerminal",{"type":415,"tag":783,"props":3382,"children":3383},{"style":790},[3384],{"type":420,"value":3385},"\"\n",{"type":415,"tag":783,"props":3387,"children":3388},{"class":785,"line":840},[3389],{"type":415,"tag":783,"props":3390,"children":3391},{"style":1362},[3392],{"type":420,"value":3393},"RefreshEnv\n",{"type":415,"tag":783,"props":3395,"children":3396},{"class":785,"line":866},[3397,3402,3407,3413,3418,3423,3427,3432,3437,3442,3447],{"type":415,"tag":783,"props":3398,"children":3399},{"style":1362},[3400],{"type":420,"value":3401},"git clone https:",{"type":415,"tag":783,"props":3403,"children":3404},{"style":790},[3405],{"type":420,"value":3406},"//",{"type":415,"tag":783,"props":3408,"children":3410},{"style":3409},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[3411],{"type":420,"value":3412},"github.com",{"type":415,"tag":783,"props":3414,"children":3415},{"style":790},[3416],{"type":420,"value":3417},"/",{"type":415,"tag":783,"props":3419,"children":3420},{"style":1362},[3421],{"type":420,"value":3422},"TechWatching",{"type":415,"tag":783,"props":3424,"children":3425},{"style":790},[3426],{"type":420,"value":3417},{"type":415,"tag":783,"props":3428,"children":3429},{"style":1362},[3430],{"type":420,"value":3431},"dotfiles.git ",{"type":415,"tag":783,"props":3433,"children":3434},{"style":790},[3435],{"type":420,"value":3436},"\"$",{"type":415,"tag":783,"props":3438,"children":3439},{"style":1362},[3440],{"type":420,"value":3441},"env:USERPROFILE",{"type":415,"tag":783,"props":3443,"children":3444},{"style":825},[3445],{"type":420,"value":3446},"\\dotfiles",{"type":415,"tag":783,"props":3448,"children":3449},{"style":790},[3450],{"type":420,"value":3385},{"type":415,"tag":416,"props":3452,"children":3453},{},[3454,3456,3462],{"type":420,"value":3455},"The scripts are written in ",{"type":415,"tag":423,"props":3457,"children":3460},{"href":3458,"rel":3459},"https://docs.microsoft.com/en-us/powershell/",[427],[3461],{"type":420,"value":249},{"type":420,"value":755},{"type":415,"tag":443,"props":3464,"children":3466},{"id":3465},"installing-software",[3467],{"type":420,"value":3468},"Installing software",{"type":415,"tag":616,"props":3470,"children":3472},{"id":3471},"package-managers",[3473],{"type":420,"value":3474},"Package managers",{"type":415,"tag":416,"props":3476,"children":3477},{},[3478,3480,3486,3488,3495],{"type":420,"value":3479},"The easiest way to automate installing, upgrading, and configuring software is to use package managers. There are several available on Windows but I chose to use ",{"type":415,"tag":423,"props":3481,"children":3484},{"href":3482,"rel":3483},"https://docs.microsoft.com/en-us/windows/package-manager/",[427],[3485],{"type":420,"value":2231},{"type":420,"value":3487}," and ",{"type":415,"tag":423,"props":3489,"children":3492},{"href":3490,"rel":3491},"https://chocolatey.org/",[427],[3493],{"type":420,"value":3494},"Chocolatey",{"type":420,"value":755},{"type":415,"tag":416,"props":3497,"children":3498},{},[3499],{"type":420,"value":3500},"Windows Package Manager (aka winget) is relatively new so has fewer features and packages available than other package managers like Chocolatey. However it's very promising, it's getting better every day, it's built-in Windows, and above all, it supports installing Microsoft Store apps. So I chose to use winget by default and fallback to Chocolatey when a package I needed was not available or not up-to-date in winget.",{"type":415,"tag":588,"props":3502,"children":3503},{"icon":590},[3504],{"type":415,"tag":416,"props":3505,"children":3506},{},[3507,3509,3514],{"type":420,"value":3508},"I learned a few things about winget while starting to use it. You can find my articles talking about it ",{"type":415,"tag":423,"props":3510,"children":3512},{"href":2262,"rel":3511},[427],[3513],{"type":420,"value":2266},{"type":420,"value":3515}," if you are interested.",{"type":415,"tag":616,"props":3517,"children":3519},{"id":3518},"installing-integrated-development-environments-ides",[3520],{"type":420,"value":3521},"Installing Integrated Development Environments (IDEs)",{"type":415,"tag":416,"props":3523,"children":3524},{},[3525],{"type":420,"value":3526},"Visual Studio has been my main IDE for a while, but I have been started recently to use more and more Rider as well. Both are nice IDEs with many features particularly well suited for .NET development which is what I mostly do. I have been a Visual Studio Code long-time user too, mostly using it as a text editor or for front development. I also use vscode to work with Azure because it has nice extensions for a lot of Azure services.",{"type":415,"tag":416,"props":3528,"children":3529},{},[3530],{"type":415,"tag":1322,"props":3531,"children":3535},{"alt":3532,"className":3533,"src":3534},"Visual Studio logo.",[1326,1327],"/posts/images/automate_developer_machine_visualstudio.jpg",[],{"type":415,"tag":416,"props":3537,"children":3538},{},[3539],{"type":420,"value":3540},"You probably want to customize your Visual Studio installation to only include the workloads, components, and language packs you are using. You can specify them as parameters in the winget or chocolatey install command but that's a bit cumbersome. The easiest way is to export your configuration from the Visual Studio installer.",{"type":415,"tag":416,"props":3542,"children":3543},{},[3544],{"type":420,"value":3545},"You can then install Visual Studio with this kind of command:",{"type":415,"tag":773,"props":3547,"children":3549},{"className":2236,"code":3548,"language":248,"meta":401,"style":401},"winget install -e -h --id Microsoft.VisualStudio.2022.Enterprise --silent --override \"--wait --quiet --addProductLang En-us --config .vsconfig\"\n",[3550],{"type":415,"tag":550,"props":3551,"children":3552},{"__ignoreMap":401},[3553],{"type":415,"tag":783,"props":3554,"children":3555},{"class":785,"line":786},[3556,3561,3565,3570,3574,3579,3583,3588,3593,3598,3602,3607,3611,3616,3620,3625],{"type":415,"tag":783,"props":3557,"children":3558},{"style":1362},[3559],{"type":420,"value":3560},"winget install ",{"type":415,"tag":783,"props":3562,"children":3563},{"style":790},[3564],{"type":420,"value":3356},{"type":415,"tag":783,"props":3566,"children":3567},{"style":1362},[3568],{"type":420,"value":3569},"e ",{"type":415,"tag":783,"props":3571,"children":3572},{"style":790},[3573],{"type":420,"value":3356},{"type":415,"tag":783,"props":3575,"children":3576},{"style":1362},[3577],{"type":420,"value":3578},"h ",{"type":415,"tag":783,"props":3580,"children":3581},{"style":790},[3582],{"type":420,"value":3366},{"type":415,"tag":783,"props":3584,"children":3585},{"style":1362},[3586],{"type":420,"value":3587},"id Microsoft.VisualStudio.",{"type":415,"tag":783,"props":3589,"children":3590},{"style":902},[3591],{"type":420,"value":3592},"2022.",{"type":415,"tag":783,"props":3594,"children":3595},{"style":1362},[3596],{"type":420,"value":3597},"Enterprise ",{"type":415,"tag":783,"props":3599,"children":3600},{"style":790},[3601],{"type":420,"value":3366},{"type":415,"tag":783,"props":3603,"children":3604},{"style":1362},[3605],{"type":420,"value":3606},"silent ",{"type":415,"tag":783,"props":3608,"children":3609},{"style":790},[3610],{"type":420,"value":3366},{"type":415,"tag":783,"props":3612,"children":3613},{"style":1362},[3614],{"type":420,"value":3615},"override ",{"type":415,"tag":783,"props":3617,"children":3618},{"style":790},[3619],{"type":420,"value":813},{"type":415,"tag":783,"props":3621,"children":3622},{"style":825},[3623],{"type":420,"value":3624},"--wait --quiet --addProductLang En-us --config .vsconfig",{"type":415,"tag":783,"props":3626,"children":3627},{"style":790},[3628],{"type":420,"value":3385},{"type":415,"tag":588,"props":3630,"children":3631},{"icon":590},[3632],{"type":415,"tag":416,"props":3633,"children":3634},{},[3635],{"type":420,"value":3636},"I did not know before trying to automate Visual Studio installation that you could specify parameters to its installer on the command line but that's very useful.",{"type":415,"tag":416,"props":3638,"children":3639},{},[3640],{"type":420,"value":3641},"I don't export Visual Studio settings or extensions as most of them can be synchronized through the personalization account (you just have to sign in to Visual Studio with the same account).",{"type":415,"tag":416,"props":3643,"children":3644},{},[3645,3647,3654],{"type":420,"value":3646},"In a similar way, I use ",{"type":415,"tag":423,"props":3648,"children":3651},{"href":3649,"rel":3650},"https://code.visualstudio.com/docs/editor/settings-sync",[427],[3652],{"type":420,"value":3653},"vscode built-in settings synchronization",{"type":420,"value":3655}," to share my setup (extensions, settings, keybindings) across machines so I don't have to do a custom installation.",{"type":415,"tag":416,"props":3657,"children":3658},{},[3659,3661,3668],{"type":420,"value":3660},"There is a Rider package available on chocolatey but it is not always up-to-date and I prefer to use the JetBrains ToolBox application to manage the installation and update of my JetBrains tools. Unfortunately, JetBrains ToolBox does not currently have a CLI to automate the installation of Rider and other useful .NET tools (there is an ",{"type":415,"tag":423,"props":3662,"children":3665},{"href":3663,"rel":3664},"https://youtrack.jetbrains.com/issue/TBX-653/toolbox-cli",[427],[3666],{"type":420,"value":3667},"issue",{"type":420,"value":3669}," opened though if you want to vote) so I only automated the installation of JetBrains ToolBox and will have to install it Rider manually from it.",{"type":415,"tag":616,"props":3671,"children":3673},{"id":3672},"tools-i-need",[3674],{"type":420,"value":3675},"Tools I need",{"type":415,"tag":416,"props":3677,"children":3678},{},[3679,3681,3687,3689,3694],{"type":420,"value":3680},"Of course, git is a must-have. I use ",{"type":415,"tag":423,"props":3682,"children":3684},{"href":1931,"rel":3683},[427],[3685],{"type":420,"value":3686},"NVM for windows",{"type":420,"value":3688}," to manage multiple installations of node.js on my laptop, and ",{"type":415,"tag":423,"props":3690,"children":3692},{"href":2291,"rel":3691},[427],[3693],{"type":420,"value":362},{"type":420,"value":3695}," is my preferred package manager for Node.js.",{"type":415,"tag":416,"props":3697,"children":3698},{},[3699,3701,3707,3708,3715,3717,3724,3726,3732],{"type":420,"value":3700},"There are 2 shells I like to use (both of them are cross-platform): ",{"type":415,"tag":423,"props":3702,"children":3705},{"href":3703,"rel":3704},"https://docs.microsoft.com/en-us/powershell/scripting/overview",[427],[3706],{"type":420,"value":249},{"type":420,"value":3487},{"type":415,"tag":423,"props":3709,"children":3712},{"href":3710,"rel":3711},"https://www.nushell.sh/",[427],[3713],{"type":420,"value":3714},"Nushell",{"type":420,"value":3716},". I use ",{"type":415,"tag":423,"props":3718,"children":3721},{"href":3719,"rel":3720},"https://ohmyposh.dev/",[427],[3722],{"type":420,"value":3723},"Oh My Posh",{"type":420,"value":3725}," to configure an enhanced prompt for both shells. ",{"type":415,"tag":423,"props":3727,"children":3730},{"href":3728,"rel":3729},"https://docs.microsoft.com/en-us/windows/terminal/",[427],[3731],{"type":420,"value":275},{"type":420,"value":3733}," is my go-to terminal.",{"type":415,"tag":416,"props":3735,"children":3736},{},[3737],{"type":420,"value":3738},"As I work mainly on cloud projects, there are some Azure tools I need on my laptop:",{"type":415,"tag":773,"props":3740,"children":3742},{"className":2236,"code":3741,"language":248,"meta":401,"style":401},"winget install -e -h --id Microsoft.AzureCLI\nwinget install -e -h --id Microsoft.AzureCosmosEmulator\nwinget install -e -h --id Microsoft.AzureDataStudio\nwinget install -e -h --id Microsoft.azure-iot-explorer\nwinget install -e -h --id Microsoft.AzureStorageExplorer\nwinget install -e -h --id Pulumi.Pulumi\nwinget install -e -h --id Microsoft.AzureFunctionsCoreTools\n# Azurite can be installed through vscode extension or as a global npm package\n# pnpm add -g azurite\n",[3743],{"type":415,"tag":550,"props":3744,"children":3745},{"__ignoreMap":401},[3746,3778,3810,3842,3892,3924,3956,3988,3996],{"type":415,"tag":783,"props":3747,"children":3748},{"class":785,"line":786},[3749,3753,3757,3761,3765,3769,3773],{"type":415,"tag":783,"props":3750,"children":3751},{"style":1362},[3752],{"type":420,"value":3560},{"type":415,"tag":783,"props":3754,"children":3755},{"style":790},[3756],{"type":420,"value":3356},{"type":415,"tag":783,"props":3758,"children":3759},{"style":1362},[3760],{"type":420,"value":3569},{"type":415,"tag":783,"props":3762,"children":3763},{"style":790},[3764],{"type":420,"value":3356},{"type":415,"tag":783,"props":3766,"children":3767},{"style":1362},[3768],{"type":420,"value":3578},{"type":415,"tag":783,"props":3770,"children":3771},{"style":790},[3772],{"type":420,"value":3366},{"type":415,"tag":783,"props":3774,"children":3775},{"style":1362},[3776],{"type":420,"value":3777},"id Microsoft.AzureCLI\n",{"type":415,"tag":783,"props":3779,"children":3780},{"class":785,"line":796},[3781,3785,3789,3793,3797,3801,3805],{"type":415,"tag":783,"props":3782,"children":3783},{"style":1362},[3784],{"type":420,"value":3560},{"type":415,"tag":783,"props":3786,"children":3787},{"style":790},[3788],{"type":420,"value":3356},{"type":415,"tag":783,"props":3790,"children":3791},{"style":1362},[3792],{"type":420,"value":3569},{"type":415,"tag":783,"props":3794,"children":3795},{"style":790},[3796],{"type":420,"value":3356},{"type":415,"tag":783,"props":3798,"children":3799},{"style":1362},[3800],{"type":420,"value":3578},{"type":415,"tag":783,"props":3802,"children":3803},{"style":790},[3804],{"type":420,"value":3366},{"type":415,"tag":783,"props":3806,"children":3807},{"style":1362},[3808],{"type":420,"value":3809},"id Microsoft.AzureCosmosEmulator\n",{"type":415,"tag":783,"props":3811,"children":3812},{"class":785,"line":840},[3813,3817,3821,3825,3829,3833,3837],{"type":415,"tag":783,"props":3814,"children":3815},{"style":1362},[3816],{"type":420,"value":3560},{"type":415,"tag":783,"props":3818,"children":3819},{"style":790},[3820],{"type":420,"value":3356},{"type":415,"tag":783,"props":3822,"children":3823},{"style":1362},[3824],{"type":420,"value":3569},{"type":415,"tag":783,"props":3826,"children":3827},{"style":790},[3828],{"type":420,"value":3356},{"type":415,"tag":783,"props":3830,"children":3831},{"style":1362},[3832],{"type":420,"value":3578},{"type":415,"tag":783,"props":3834,"children":3835},{"style":790},[3836],{"type":420,"value":3366},{"type":415,"tag":783,"props":3838,"children":3839},{"style":1362},[3840],{"type":420,"value":3841},"id Microsoft.AzureDataStudio\n",{"type":415,"tag":783,"props":3843,"children":3844},{"class":785,"line":866},[3845,3849,3853,3857,3861,3865,3869,3874,3878,3883,3887],{"type":415,"tag":783,"props":3846,"children":3847},{"style":1362},[3848],{"type":420,"value":3560},{"type":415,"tag":783,"props":3850,"children":3851},{"style":790},[3852],{"type":420,"value":3356},{"type":415,"tag":783,"props":3854,"children":3855},{"style":1362},[3856],{"type":420,"value":3569},{"type":415,"tag":783,"props":3858,"children":3859},{"style":790},[3860],{"type":420,"value":3356},{"type":415,"tag":783,"props":3862,"children":3863},{"style":1362},[3864],{"type":420,"value":3578},{"type":415,"tag":783,"props":3866,"children":3867},{"style":790},[3868],{"type":420,"value":3366},{"type":415,"tag":783,"props":3870,"children":3871},{"style":1362},[3872],{"type":420,"value":3873},"id Microsoft.azure",{"type":415,"tag":783,"props":3875,"children":3876},{"style":790},[3877],{"type":420,"value":3356},{"type":415,"tag":783,"props":3879,"children":3880},{"style":1362},[3881],{"type":420,"value":3882},"iot",{"type":415,"tag":783,"props":3884,"children":3885},{"style":790},[3886],{"type":420,"value":3356},{"type":415,"tag":783,"props":3888,"children":3889},{"style":1362},[3890],{"type":420,"value":3891},"explorer\n",{"type":415,"tag":783,"props":3893,"children":3894},{"class":785,"line":893},[3895,3899,3903,3907,3911,3915,3919],{"type":415,"tag":783,"props":3896,"children":3897},{"style":1362},[3898],{"type":420,"value":3560},{"type":415,"tag":783,"props":3900,"children":3901},{"style":790},[3902],{"type":420,"value":3356},{"type":415,"tag":783,"props":3904,"children":3905},{"style":1362},[3906],{"type":420,"value":3569},{"type":415,"tag":783,"props":3908,"children":3909},{"style":790},[3910],{"type":420,"value":3356},{"type":415,"tag":783,"props":3912,"children":3913},{"style":1362},[3914],{"type":420,"value":3578},{"type":415,"tag":783,"props":3916,"children":3917},{"style":790},[3918],{"type":420,"value":3366},{"type":415,"tag":783,"props":3920,"children":3921},{"style":1362},[3922],{"type":420,"value":3923},"id Microsoft.AzureStorageExplorer\n",{"type":415,"tag":783,"props":3925,"children":3926},{"class":785,"line":920},[3927,3931,3935,3939,3943,3947,3951],{"type":415,"tag":783,"props":3928,"children":3929},{"style":1362},[3930],{"type":420,"value":3560},{"type":415,"tag":783,"props":3932,"children":3933},{"style":790},[3934],{"type":420,"value":3356},{"type":415,"tag":783,"props":3936,"children":3937},{"style":1362},[3938],{"type":420,"value":3569},{"type":415,"tag":783,"props":3940,"children":3941},{"style":790},[3942],{"type":420,"value":3356},{"type":415,"tag":783,"props":3944,"children":3945},{"style":1362},[3946],{"type":420,"value":3578},{"type":415,"tag":783,"props":3948,"children":3949},{"style":790},[3950],{"type":420,"value":3366},{"type":415,"tag":783,"props":3952,"children":3953},{"style":1362},[3954],{"type":420,"value":3955},"id Pulumi.Pulumi\n",{"type":415,"tag":783,"props":3957,"children":3958},{"class":785,"line":959},[3959,3963,3967,3971,3975,3979,3983],{"type":415,"tag":783,"props":3960,"children":3961},{"style":1362},[3962],{"type":420,"value":3560},{"type":415,"tag":783,"props":3964,"children":3965},{"style":790},[3966],{"type":420,"value":3356},{"type":415,"tag":783,"props":3968,"children":3969},{"style":1362},[3970],{"type":420,"value":3569},{"type":415,"tag":783,"props":3972,"children":3973},{"style":790},[3974],{"type":420,"value":3356},{"type":415,"tag":783,"props":3976,"children":3977},{"style":1362},[3978],{"type":420,"value":3578},{"type":415,"tag":783,"props":3980,"children":3981},{"style":790},[3982],{"type":420,"value":3366},{"type":415,"tag":783,"props":3984,"children":3985},{"style":1362},[3986],{"type":420,"value":3987},"id Microsoft.AzureFunctionsCoreTools\n",{"type":415,"tag":783,"props":3989,"children":3990},{"class":785,"line":997},[3991],{"type":415,"tag":783,"props":3992,"children":3993},{"style":3340},[3994],{"type":420,"value":3995},"# Azurite can be installed through vscode extension or as a global npm package\n",{"type":415,"tag":783,"props":3997,"children":3998},{"class":785,"line":1023},[3999],{"type":415,"tag":783,"props":4000,"children":4001},{"style":3340},[4002],{"type":420,"value":4003},"# pnpm add -g azurite\n",{"type":415,"tag":416,"props":4005,"children":4006},{},[4007,4009,4016,4017,4024],{"type":420,"value":4008},"My script also automates the installation of other tools, not related to software development but that I often use (like ",{"type":415,"tag":423,"props":4010,"children":4013},{"href":4011,"rel":4012},"https://www.7-zip.org/",[427],[4014],{"type":420,"value":4015},"7zip",{"type":420,"value":2505},{"type":415,"tag":423,"props":4018,"children":4021},{"href":4019,"rel":4020},"https://docs.microsoft.com/en-us/windows/powertoys/",[427],[4022],{"type":420,"value":4023},"Microsoft Power Toys",{"type":420,"value":4025}," or just browsers).",{"type":415,"tag":416,"props":4027,"children":4028},{},[4029],{"type":420,"value":4030},"Generally, when the applications I want to install are available in the Microsoft Store, I specify winget to install them from there.",{"type":415,"tag":773,"props":4032,"children":4034},{"className":2236,"code":4033,"language":248,"meta":401,"style":401},"winget install -e -h --id Microsoft.WindowsTerminal -s msstore\n",[4035],{"type":415,"tag":550,"props":4036,"children":4037},{"__ignoreMap":401},[4038],{"type":415,"tag":783,"props":4039,"children":4040},{"class":785,"line":786},[4041,4045,4049,4053,4057,4061,4065,4070,4074],{"type":415,"tag":783,"props":4042,"children":4043},{"style":1362},[4044],{"type":420,"value":3560},{"type":415,"tag":783,"props":4046,"children":4047},{"style":790},[4048],{"type":420,"value":3356},{"type":415,"tag":783,"props":4050,"children":4051},{"style":1362},[4052],{"type":420,"value":3569},{"type":415,"tag":783,"props":4054,"children":4055},{"style":790},[4056],{"type":420,"value":3356},{"type":415,"tag":783,"props":4058,"children":4059},{"style":1362},[4060],{"type":420,"value":3578},{"type":415,"tag":783,"props":4062,"children":4063},{"style":790},[4064],{"type":420,"value":3366},{"type":415,"tag":783,"props":4066,"children":4067},{"style":1362},[4068],{"type":420,"value":4069},"id Microsoft.WindowsTerminal ",{"type":415,"tag":783,"props":4071,"children":4072},{"style":790},[4073],{"type":420,"value":3356},{"type":415,"tag":783,"props":4075,"children":4076},{"style":1362},[4077],{"type":420,"value":4078},"s msstore\n",{"type":415,"tag":588,"props":4080,"children":4081},{"icon":590},[4082],{"type":415,"tag":416,"props":4083,"children":4084},{},[4085],{"type":420,"value":4086},"Automating the installation of my developer machine was the opportunity to realize all the tools I was using daily, and which ones were really useful.",{"type":415,"tag":616,"props":4088,"children":4090},{"id":4089},"tools-i-dont-need",[4091],{"type":420,"value":4092},"Tools I don't need",{"type":415,"tag":416,"props":4094,"children":4095},{},[4096],{"type":420,"value":4097},"Unnecessary applications that come with Windows out of the box are uninstalled using this function:",{"type":415,"tag":773,"props":4099,"children":4101},{"className":2236,"code":4100,"language":248,"meta":401,"style":401},"function removeApp {\n    Param ([string]$appName)\n    Write-Output \"Trying to remove $appName\"\n    Get-AppxPackage $appName -AllUsers | Remove-AppxPackage\n    Get-AppXProvisionedPackage -Online | Where DisplayName -like $appName | Remove-AppxProvisionedPackage -Online\n",[4102],{"type":415,"tag":550,"props":4103,"children":4104},{"__ignoreMap":401},[4105,4122,4155,4185,4222],{"type":415,"tag":783,"props":4106,"children":4107},{"class":785,"line":786},[4108,4113,4118],{"type":415,"tag":783,"props":4109,"children":4110},{"style":805},[4111],{"type":420,"value":4112},"function",{"type":415,"tag":783,"props":4114,"children":4115},{"style":3409},[4116],{"type":420,"value":4117}," removeApp",{"type":415,"tag":783,"props":4119,"children":4120},{"style":790},[4121],{"type":420,"value":863},{"type":415,"tag":783,"props":4123,"children":4124},{"class":785,"line":796},[4125,4130,4135,4140,4145,4150],{"type":415,"tag":783,"props":4126,"children":4127},{"style":1356},[4128],{"type":420,"value":4129},"    Param",{"type":415,"tag":783,"props":4131,"children":4132},{"style":790},[4133],{"type":420,"value":4134}," ([",{"type":415,"tag":783,"props":4136,"children":4137},{"style":805},[4138],{"type":420,"value":4139},"string",{"type":415,"tag":783,"props":4141,"children":4142},{"style":790},[4143],{"type":420,"value":4144},"]$",{"type":415,"tag":783,"props":4146,"children":4147},{"style":1362},[4148],{"type":420,"value":4149},"appName",{"type":415,"tag":783,"props":4151,"children":4152},{"style":790},[4153],{"type":420,"value":4154},")\n",{"type":415,"tag":783,"props":4156,"children":4157},{"class":785,"line":840},[4158,4163,4167,4172,4177,4181],{"type":415,"tag":783,"props":4159,"children":4160},{"style":3409},[4161],{"type":420,"value":4162},"    Write-Output",{"type":415,"tag":783,"props":4164,"children":4165},{"style":790},[4166],{"type":420,"value":822},{"type":415,"tag":783,"props":4168,"children":4169},{"style":825},[4170],{"type":420,"value":4171},"Trying to remove ",{"type":415,"tag":783,"props":4173,"children":4174},{"style":790},[4175],{"type":420,"value":4176},"$",{"type":415,"tag":783,"props":4178,"children":4179},{"style":1362},[4180],{"type":420,"value":4149},{"type":415,"tag":783,"props":4182,"children":4183},{"style":790},[4184],{"type":420,"value":3385},{"type":415,"tag":783,"props":4186,"children":4187},{"class":785,"line":866},[4188,4193,4198,4203,4207,4212,4217],{"type":415,"tag":783,"props":4189,"children":4190},{"style":3409},[4191],{"type":420,"value":4192},"    Get-AppxPackage",{"type":415,"tag":783,"props":4194,"children":4195},{"style":790},[4196],{"type":420,"value":4197}," $",{"type":415,"tag":783,"props":4199,"children":4200},{"style":1362},[4201],{"type":420,"value":4202},"appName ",{"type":415,"tag":783,"props":4204,"children":4205},{"style":790},[4206],{"type":420,"value":3356},{"type":415,"tag":783,"props":4208,"children":4209},{"style":1362},[4210],{"type":420,"value":4211},"AllUsers ",{"type":415,"tag":783,"props":4213,"children":4214},{"style":790},[4215],{"type":420,"value":4216},"|",{"type":415,"tag":783,"props":4218,"children":4219},{"style":3409},[4220],{"type":420,"value":4221}," Remove-AppxPackage\n",{"type":415,"tag":783,"props":4223,"children":4224},{"class":785,"line":893},[4225,4230,4235,4240,4244,4249,4254,4259,4263,4267,4271,4276,4280],{"type":415,"tag":783,"props":4226,"children":4227},{"style":3409},[4228],{"type":420,"value":4229},"    Get-AppXProvisionedPackage",{"type":415,"tag":783,"props":4231,"children":4232},{"style":790},[4233],{"type":420,"value":4234}," -",{"type":415,"tag":783,"props":4236,"children":4237},{"style":1362},[4238],{"type":420,"value":4239},"Online ",{"type":415,"tag":783,"props":4241,"children":4242},{"style":790},[4243],{"type":420,"value":4216},{"type":415,"tag":783,"props":4245,"children":4246},{"style":1356},[4247],{"type":420,"value":4248}," Where",{"type":415,"tag":783,"props":4250,"children":4251},{"style":1362},[4252],{"type":420,"value":4253}," DisplayName ",{"type":415,"tag":783,"props":4255,"children":4256},{"style":790},[4257],{"type":420,"value":4258},"-like",{"type":415,"tag":783,"props":4260,"children":4261},{"style":790},[4262],{"type":420,"value":4197},{"type":415,"tag":783,"props":4264,"children":4265},{"style":1362},[4266],{"type":420,"value":4202},{"type":415,"tag":783,"props":4268,"children":4269},{"style":790},[4270],{"type":420,"value":4216},{"type":415,"tag":783,"props":4272,"children":4273},{"style":3409},[4274],{"type":420,"value":4275}," Remove-AppxProvisionedPackage",{"type":415,"tag":783,"props":4277,"children":4278},{"style":790},[4279],{"type":420,"value":4234},{"type":415,"tag":783,"props":4281,"children":4282},{"style":1362},[4283],{"type":420,"value":4284},"Online\n",{"type":415,"tag":416,"props":4286,"children":4287},{},[4288,4290,4296,4298,4304],{"type":420,"value":4289},"It allows me to get rid of pre-installed applications like ",{"type":415,"tag":550,"props":4291,"children":4293},{"className":4292},[],[4294],{"type":420,"value":4295},"Facebook",{"type":420,"value":4297},"or ",{"type":415,"tag":550,"props":4299,"children":4301},{"className":4300},[],[4302],{"type":420,"value":4303},"Bing News",{"type":420,"value":755},{"type":415,"tag":443,"props":4306,"children":4308},{"id":4307},"configuring-software",[4309],{"type":420,"value":4310},"Configuring software",{"type":415,"tag":416,"props":4312,"children":4313},{},[4314],{"type":420,"value":4315},"Installing software is one part, but configuring it is another. As I previously mentioned, for some applications in which you sign in with an account, settings are synchronized so you don't need to transfer the application settings from your previous laptop. For the others, I decided to use symbolic links.",{"type":415,"tag":416,"props":4317,"children":4318},{},[4319],{"type":415,"tag":1322,"props":4320,"children":4324},{"alt":4321,"className":4322,"src":4323},"A light bulb that is turned on.",[1326,1327],"/posts/images/automate_developer_machine_idea.jpg",[],{"type":415,"tag":416,"props":4326,"children":4327},{},[4328],{"type":420,"value":4329},"Here are some examples of settings I wanted to set up on a new laptop:",{"type":415,"tag":493,"props":4331,"children":4332},{},[4333,4345,4356,4367],{"type":415,"tag":497,"props":4334,"children":4335},{},[4336,4338],{"type":420,"value":4337},"my ",{"type":415,"tag":423,"props":4339,"children":4342},{"href":4340,"rel":4341},"https://github.com/TechWatching/dotfiles/blob/main/config/git/.gitconfig",[427],[4343],{"type":420,"value":4344},".gitconfig file",{"type":415,"tag":497,"props":4346,"children":4347},{},[4348,4349],{"type":420,"value":4337},{"type":415,"tag":423,"props":4350,"children":4353},{"href":4351,"rel":4352},"https://github.com/TechWatching/dotfiles/blob/main/config/windowsTerminal/settings.json",[427],[4354],{"type":420,"value":4355},"Windows Terminal settings",{"type":415,"tag":497,"props":4357,"children":4358},{},[4359,4360],{"type":420,"value":4337},{"type":415,"tag":423,"props":4361,"children":4364},{"href":4362,"rel":4363},"https://github.com/TechWatching/dotfiles/blob/main/config/powershell/Microsoft.PowerShell_profile.ps1",[427],[4365],{"type":420,"value":4366},"PowerShell profile",{"type":415,"tag":497,"props":4368,"children":4369},{},[4370,4371,4378],{"type":420,"value":4337},{"type":415,"tag":423,"props":4372,"children":4375},{"href":4373,"rel":4374},"https://github.com/TechWatching/dotfiles/blob/main/config/prompt/.oh-my-posh.omp.json",[427],[4376],{"type":420,"value":4377},"custom prompt configuration",{"type":420,"value":4379}," done using oh my posh",{"type":415,"tag":416,"props":4381,"children":4382},{},[4383,4385,4390],{"type":420,"value":4384},"To create a symbolic link at the Windows Terminal settings file pointing to the settings file in my ",{"type":415,"tag":550,"props":4386,"children":4388},{"className":4387},[],[4389],{"type":420,"value":3284},{"type":420,"value":4391}," cloned repository I can do the following command:",{"type":415,"tag":773,"props":4393,"children":4395},{"className":2236,"code":4394,"language":248,"meta":401,"style":401},"New-Item -ItemType SymbolicLink -Path \"$env:USERPROFILE\\AppData\\Local\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\settings.json\" -Target \"$env:USERPROFILE\\dotfiles\\config\\windowsTerminal\\settings.json\"\"\n",[4396],{"type":415,"tag":550,"props":4397,"children":4398},{"__ignoreMap":401},[4399],{"type":415,"tag":783,"props":4400,"children":4401},{"class":785,"line":786},[4402,4407,4411,4416,4420,4425,4429,4433,4438,4442,4446,4451,4455,4459,4464],{"type":415,"tag":783,"props":4403,"children":4404},{"style":3409},[4405],{"type":420,"value":4406},"New-Item",{"type":415,"tag":783,"props":4408,"children":4409},{"style":790},[4410],{"type":420,"value":4234},{"type":415,"tag":783,"props":4412,"children":4413},{"style":1362},[4414],{"type":420,"value":4415},"ItemType SymbolicLink ",{"type":415,"tag":783,"props":4417,"children":4418},{"style":790},[4419],{"type":420,"value":3356},{"type":415,"tag":783,"props":4421,"children":4422},{"style":1362},[4423],{"type":420,"value":4424},"Path ",{"type":415,"tag":783,"props":4426,"children":4427},{"style":790},[4428],{"type":420,"value":3436},{"type":415,"tag":783,"props":4430,"children":4431},{"style":1362},[4432],{"type":420,"value":3441},{"type":415,"tag":783,"props":4434,"children":4435},{"style":825},[4436],{"type":420,"value":4437},"\\AppData\\Local\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\settings.json",{"type":415,"tag":783,"props":4439,"children":4440},{"style":790},[4441],{"type":420,"value":813},{"type":415,"tag":783,"props":4443,"children":4444},{"style":790},[4445],{"type":420,"value":4234},{"type":415,"tag":783,"props":4447,"children":4448},{"style":1362},[4449],{"type":420,"value":4450},"Target ",{"type":415,"tag":783,"props":4452,"children":4453},{"style":790},[4454],{"type":420,"value":3436},{"type":415,"tag":783,"props":4456,"children":4457},{"style":1362},[4458],{"type":420,"value":3441},{"type":415,"tag":783,"props":4460,"children":4461},{"style":825},[4462],{"type":420,"value":4463},"\\dotfiles\\config\\windowsTerminal\\settings.json",{"type":415,"tag":783,"props":4465,"children":4466},{"style":1362},[4467],{"type":420,"value":4468},"\"\"\n",{"type":415,"tag":416,"props":4470,"children":4471},{},[4472],{"type":420,"value":4473},"Because I use symbolic links, when I modify the configuration of my Windows Terminal (event through the UI), the settings file in my git repository is the one being modified so I can commit it and push it so that my other machines have the latest version. And it's the same for all my settings files.",{"type":415,"tag":416,"props":4475,"children":4476},{},[4477],{"type":420,"value":4478},"There are others settings in my repository and I will probably continue to add some later.",{"type":415,"tag":443,"props":4480,"children":4482},{"id":4481},"what-i-did-not-automate-yet",[4483],{"type":420,"value":4484},"What I did not automate ... yet!",{"type":415,"tag":416,"props":4486,"children":4487},{},[4488],{"type":420,"value":4489},"There are many things I did not automate yet and that I will do bit by bit.  Nevertheless, I am happy with the current setup. There is much room for improvement but I've already won some time last time I changed my laptop, and that was the goal.",{"type":415,"tag":416,"props":4491,"children":4492},{},[4493,4495,4502,4504,4510],{"type":420,"value":4494},"As I've just said, there are other configurations I would probably need to store in my repository. ",{"type":415,"tag":423,"props":4496,"children":4499},{"href":4497,"rel":4498},"https://docs.microsoft.com/en-us/windows/powertoys",[427],[4500],{"type":420,"value":4501},"Power Toys",{"type":420,"value":4503}," settings might be one of them unless they add a settings sync (issue already opened ",{"type":415,"tag":423,"props":4505,"children":4508},{"href":4506,"rel":4507},"https://github.com/microsoft/PowerToys/issues/3381",[427],[4509],{"type":420,"value":2266},{"type":420,"value":4511},"). It would be better because there are many settings files so only using symbolic links could be cumbersome.",{"type":415,"tag":416,"props":4513,"children":4514},{},[4515,4517,4524],{"type":420,"value":4516},"Most of the software I need is installed by my setup script. One exception is JetBrains products but I hope it will be possible soon. I have some commented lines in my installation script about automating the setup of ",{"type":415,"tag":423,"props":4518,"children":4521},{"href":4519,"rel":4520},"https://docs.microsoft.com/en-us/windows/wsl/install",[427],[4522],{"type":420,"value":4523},"WSL2",{"type":420,"value":4525}," and docker (well installation of Rancher Desktop to be more precise as a replacement of Docker Desktop due to its new license model) that required some windows features to be installed.  I think these lines should work with some adjustments, it's just that I did that manually last time and did not take the time to test the corresponding commands properly.",{"type":415,"tag":416,"props":4527,"children":4528},{},[4529],{"type":420,"value":4530},"There are also some limitations to my current approach to installing my developer machine:",{"type":415,"tag":493,"props":4532,"children":4533},{},[4534,4548,4553],{"type":415,"tag":497,"props":4535,"children":4536},{},[4537,4539,4546],{"type":420,"value":4538},"As everything is contained in a public GitHub repository, I can't keep some files or secrets private obviously (it could be useful to configure a new machine to ",{"type":415,"tag":423,"props":4540,"children":4543},{"href":4541,"rel":4542},"https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits",[427],[4544],{"type":420,"value":4545},"sign commits",{"type":420,"value":4547}," for instance)",{"type":415,"tag":497,"props":4549,"children":4550},{},[4551],{"type":420,"value":4552},"The installation script is not idempotent so it will try to re-install and re-configure everything if I re-run it (not a big deal as I mainly use it to install a new machine, but it would be nice to be able to just re-run it to get on an existing installation all the latest improvements I made on another laptop)",{"type":415,"tag":497,"props":4554,"children":4555},{},[4556],{"type":420,"value":4557},"I did not set up anything to handle multiple configurations (one for my personal laptop and one for my professional laptop for example)",{"type":415,"tag":416,"props":4559,"children":4560},{},[4561,4563,4570],{"type":420,"value":4562},"I think the tool ",{"type":415,"tag":423,"props":4564,"children":4567},{"href":4565,"rel":4566},"https://www.chezmoi.io/",[427],[4568],{"type":420,"value":4569},"chezmoi",{"type":420,"value":4571}," might help to solve some of these limitations. Maybe one day I'll take the time to dig a little deeper to see if I can improve the whole process.",{"type":415,"tag":443,"props":4573,"children":4575},{"id":4574},"to-conclude",[4576],{"type":420,"value":4577},"To conclude",{"type":415,"tag":416,"props":4579,"children":4580},{},[4581,4583,4589],{"type":420,"value":4582},"You can find the repository with all the code I use to automate my installation ",{"type":415,"tag":423,"props":4584,"children":4587},{"href":4585,"rel":4586},"https://github.com/TechWatching/dotfiles",[427],[4588],{"type":420,"value":2266},{"type":420,"value":755},{"type":415,"tag":416,"props":4591,"children":4592},{},[4593],{"type":420,"value":4594},"Automating my machine installation was not that fun, but I learned lots of things while doing it about package managers, windows, symbolic links, PowerShell... And in the end, it is very satisfying when everything starts to install automatically and you find a familiar environment on a new laptop.",{"type":415,"tag":416,"props":4596,"children":4597},{},[4598],{"type":420,"value":4599},"Whether you adopt this approach or another to automate the setup of your development machine, I think it's an important thing to do. It does not have to be complex, and you don't need to automate everything. But you should probably at least use a package manager for your software installations, have a list somewhere of the most useful tools you need, and keep a copy of the configuration files of your most important tools.",{"type":415,"tag":416,"props":4601,"children":4602},{},[4603,4605,4612],{"type":420,"value":4604},"This article is part of the ",{"type":415,"tag":423,"props":4606,"children":4609},{"href":4607,"rel":4608},"https://townhall.hashnode.com/4-articles-in-4-weeks-hashnode-writing-contest",[427],[4610],{"type":420,"value":4611},"4 articles in 4 weeks Hashnode writing contest",{"type":420,"value":755},{"type":415,"tag":416,"props":4614,"children":4615},{},[4616],{"type":420,"value":4617},"Happy learning.",{"type":415,"tag":1420,"props":4619,"children":4620},{},[4621],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":4623},[4624,4625,4626,4632,4633,4634],{"id":445,"depth":796,"text":448},{"id":3179,"depth":796,"text":3182},{"id":3465,"depth":796,"text":3468,"children":4627},[4628,4629,4630,4631],{"id":3471,"depth":840,"text":3474},{"id":3518,"depth":840,"text":3521},{"id":3672,"depth":840,"text":3675},{"id":4089,"depth":840,"text":4092},{"id":4307,"depth":796,"text":4310},{"id":4481,"depth":796,"text":4484},{"id":4574,"depth":796,"text":4577},"content:1.posts:42.automate-developer-machine.md","1.posts/42.automate-developer-machine.md",{"_path":124,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":123,"description":4638,"lead":4639,"date":4640,"image":4641,"badge":4643,"tags":4644,"body":4645,"_type":1435,"_id":5643,"_source":1437,"_file":5644,"_extension":1439},"When using some API Clients (like REST Client or the HTTP Client of JetBrains' IDEs), environment variables are stored in JSON files that can contain secrets. To share these files within a team, developers tend to send them by email or by messaging applications, which is not very convenient nor secure 🔐. I thought it would be a good idea to store these secrets directly in an Azure Key Vault and automate the generation of a JSON file containing the secrets using Azure CLI and Nushell.","Playing with Azure CLI and Nushell to generate a secret environment file to send HTTP requests","2022-08-01T00:00:00.000Z",{"src":4642},"/images/padlock_1.jpg",{"label":408},[206,213,243,225,260],{"type":412,"children":4646,"toc":5637},[4647,4680,4686,4718,4758,4808,4817,4822,4843,4849,4854,4862,4867,4873,4893,4906,4915,4926,5090,5095,5104,5109,5118,5130,5139,5155,5167,5176,5196,5205,5218,5227,5255,5260,5269,5282,5290,5295,5304,5316,5325,5337,5342,5605,5609,5614,5628,5633],{"type":415,"tag":416,"props":4648,"children":4649},{},[4650,4652,4657,4659,4665,4667,4673,4674,4679],{"type":420,"value":4651},"When using some API Clients (like ",{"type":415,"tag":423,"props":4653,"children":4655},{"href":2508,"rel":4654},[427],[4656],{"type":420,"value":2512},{"type":420,"value":4658}," or the ",{"type":415,"tag":423,"props":4660,"children":4662},{"href":2796,"rel":4661},[427],[4663],{"type":420,"value":4664},"HTTP Client of JetBrains' IDEs",{"type":420,"value":4666},"), environment variables are stored in JSON files that can contain secrets. To share these files within a team, developers tend to send them by email or by messaging applications, which is not very convenient nor secure 🔐. I thought it would be a good idea to store these secrets directly in an Azure Key Vault and automate the generation of a JSON file containing the secrets using ",{"type":415,"tag":423,"props":4668,"children":4671},{"href":4669,"rel":4670},"https://docs.microsoft.com/en-us/cli/azure/",[427],[4672],{"type":420,"value":225},{"type":420,"value":3487},{"type":415,"tag":423,"props":4675,"children":4677},{"href":3710,"rel":4676},[427],[4678],{"type":420,"value":3714},{"type":420,"value":755},{"type":415,"tag":443,"props":4681,"children":4683},{"id":4682},"the-problem-keep-secrets-secure-while-making-http-requests",[4684],{"type":420,"value":4685},"The problem: keep secrets secure while making HTTP requests",{"type":415,"tag":416,"props":4687,"children":4688},{},[4689,4691,4696,4698,4703,4705,4710,4711,4716],{"type":420,"value":4690},"If you have read my article \"",{"type":415,"tag":423,"props":4692,"children":4694},{"href":2485,"rel":4693},[427],[4695],{"type":420,"value":9},{"type":420,"value":4697},"\", you know I am a big fan of using the vscode extension ",{"type":415,"tag":550,"props":4699,"children":4701},{"className":4700},[],[4702],{"type":420,"value":2512},{"type":420,"value":4704}," to make HTTP requests instead of using GUI tools like Postman. With REST Client, you write your HTTP requests using the standard RFC 2616 in ",{"type":415,"tag":550,"props":4706,"children":4708},{"className":4707},[],[4709],{"type":420,"value":2757},{"type":420,"value":2752},{"type":415,"tag":550,"props":4712,"children":4714},{"className":4713},[],[4715],{"type":420,"value":2750},{"type":420,"value":4717}," files and commit them to your git repository. You can define environments and their associated variables in the workspace settings file of vscode (you can also store them in the user settings file but I don't recommend it as they would apply to every vscode workspace). If you have some secrets among your environment variables (like an API key for instance), you obviously can't commit this settings file (you should never commit secrets to a git repository). So sharing among your developer team the environment variables needed to run the requests can be difficult.",{"type":415,"tag":773,"props":4719,"children":4721},{"className":1345,"code":4720,"language":212,"meta":401,"style":401},"### Get Luke Skywalker\nGET https://swapi.co/api/people/?search=Luke HTTP/1.1\n",[4722],{"type":415,"tag":550,"props":4723,"children":4724},{"__ignoreMap":401},[4725,4733],{"type":415,"tag":783,"props":4726,"children":4727},{"class":785,"line":786},[4728],{"type":415,"tag":783,"props":4729,"children":4730},{"style":3340},[4731],{"type":420,"value":4732},"### Get Luke Skywalker\n",{"type":415,"tag":783,"props":4734,"children":4735},{"class":785,"line":796},[4736,4740,4745,4749,4753],{"type":415,"tag":783,"props":4737,"children":4738},{"style":1356},[4739],{"type":420,"value":1359},{"type":415,"tag":783,"props":4741,"children":4742},{"style":1362},[4743],{"type":420,"value":4744}," https://swapi.co/api/people/?search=Luke ",{"type":415,"tag":783,"props":4746,"children":4747},{"style":902},[4748],{"type":420,"value":213},{"type":415,"tag":783,"props":4750,"children":4751},{"style":1362},[4752],{"type":420,"value":3417},{"type":415,"tag":783,"props":4754,"children":4755},{"style":902},[4756],{"type":420,"value":4757},"1.1\n",{"type":415,"tag":416,"props":4759,"children":4760},{},[4761,4763,4770,4772,4777,4779,4784,4785,4790,4792,4798,4800,4806],{"type":420,"value":4762},"I have been using recently the IDE ",{"type":415,"tag":423,"props":4764,"children":4767},{"href":4765,"rel":4766},"https://www.jetbrains.com/fr-fr/rider/",[427],[4768],{"type":420,"value":4769},"Rider",{"type":420,"value":4771},", which has (like all the other JetBrains' IDEs) an integrated ",{"type":415,"tag":423,"props":4773,"children":4775},{"href":2796,"rel":4774},[427],[4776],{"type":420,"value":2800},{"type":420,"value":4778},". It's very similar to REST Client (same syntax for the requests that are written in ",{"type":415,"tag":550,"props":4780,"children":4782},{"className":4781},[],[4783],{"type":420,"value":2757},{"type":420,"value":2752},{"type":415,"tag":550,"props":4786,"children":4788},{"className":4787},[],[4789],{"type":420,"value":2750},{"type":420,"value":4791}," files) with some extra features. With this HTTP Client, environment variables are stored in a public JSON environment file ",{"type":415,"tag":550,"props":4793,"children":4795},{"className":4794},[],[4796],{"type":420,"value":4797},"http-client.env.json",{"type":420,"value":4799}," that can be committed. However, secrets can be stored in a private JSON environment file ",{"type":415,"tag":550,"props":4801,"children":4803},{"className":4802},[],[4804],{"type":420,"value":4805},"http-client.private.env.json",{"type":420,"value":4807}," that will not be committed and whose values will override the values in the public file. It's well thought out, yet we still have the problem of sharing with our team the private file containing the secrets.",{"type":415,"tag":416,"props":4809,"children":4810},{},[4811],{"type":415,"tag":1322,"props":4812,"children":4816},{"alt":4813,"className":4814,"src":4815},"HTTP file, HTTP environment file, and HTTP private environment opnened file in Rider.",[1326,1327],"/posts/images/httpclientssecrets_rider_1.png",[],{"type":415,"tag":416,"props":4818,"children":4819},{},[4820],{"type":420,"value":4821},"When someone joins the team or new environment variables have been added, the developer in the team that has the latest version of the environment file usually share it by sending it by email or private message in Microsoft Teams, or Slack... to those who need it. This is not very convenient and this is not a good practice because you don't want secrets floating around. So what can we do about that?",{"type":415,"tag":588,"props":4823,"children":4824},{"icon":590},[4825],{"type":415,"tag":416,"props":4826,"children":4827},{},[4828,4830,4835,4836,4841],{"type":420,"value":4829},"To be honest, even if sharing secrets like that bothered me a bit, I only decided to think of a solution when a friend pointed out to me that the big challenge with tools like ",{"type":415,"tag":550,"props":4831,"children":4833},{"className":4832},[],[4834],{"type":420,"value":2512},{"type":420,"value":2752},{"type":415,"tag":550,"props":4837,"children":4839},{"className":4838},[],[4840],{"type":420,"value":2800},{"type":420,"value":4842}," from JetBrains was managing secrets.",{"type":415,"tag":443,"props":4844,"children":4846},{"id":4845},"the-solution-use-azure-key-vault-and-scripting",[4847],{"type":420,"value":4848},"The solution: use Azure Key Vault and scripting",{"type":415,"tag":416,"props":4850,"children":4851},{},[4852],{"type":420,"value":4853},"The solution is not complicated. I asked myself: where do I usually store secrets? The answer is \"a vault\". Whether it is Azure Key Vault, AWS Secret Manager, Google Cloud Secret Manager, or HashiCorp Vault it does not matter, secrets have to be stored somewhere safe, and it's precisely the purpose of a vault 🔒. I use Azure Key Vault when developing applications so that's what I am going to use as well for secrets needed for sending HTTP requests. If I want my team to be able to retrieve the secrets I just have to ensure everyone has access to the Key Vault.",{"type":415,"tag":588,"props":4855,"children":4856},{"icon":728},[4857],{"type":415,"tag":416,"props":4858,"children":4859},{},[4860],{"type":420,"value":4861},"By the way, I like to create an Azure AD Group for my team so that all the permissions given in Azure (for the project the team is working on) are assigned to this group instead of to each developer. When someone joins or leaves the team, we then can simply add him to the group or remove him from it.",{"type":415,"tag":416,"props":4863,"children":4864},{},[4865],{"type":420,"value":4866},"If the secrets are stored in an Azure Key Vault, we can let each developer retrieve the secrets from the vault and put them in their private environment file. But honestly, it's not convenient, especially with many secrets. A better solution is to make a script that automatically retrieves the secrets and generates the JSON file. That way the git repository will contain the HTTP requests, the public environment file, and a script to generate the private environment file so that any new joiner will have everything he needs to get started and run the requests.",{"type":415,"tag":443,"props":4868,"children":4870},{"id":4869},"lets-script-that-with-azure-cli-and-nushell",[4871],{"type":420,"value":4872},"Let's script that with Azure CLI and Nushell!",{"type":415,"tag":416,"props":4874,"children":4875},{},[4876,4878,4884,4886,4891],{"type":420,"value":4877},"I have chosen to script that using Azure CLI and Nushell because these are 2 tools I like and I am confident the resulting script will be concise and not too difficult to write. If you are not familiar with Azure CLI, you can check my article \"",{"type":415,"tag":423,"props":4879,"children":4882},{"href":4880,"rel":4881},"https://www.techwatching.dev/posts/welcome-azure-cli",[427],[4883],{"type":420,"value":15},{"type":420,"value":4885},"\". If you don't know Nushell you can check its ",{"type":415,"tag":423,"props":4887,"children":4889},{"href":3710,"rel":4888},[427],[4890],{"type":420,"value":3231},{"type":420,"value":4892}," or just continue reading this article to see how nice this shell is.",{"type":415,"tag":416,"props":4894,"children":4895},{},[4896,4898,4904],{"type":420,"value":4897},"I have already created an Azure Key Vault named ",{"type":415,"tag":550,"props":4899,"children":4901},{"className":4900},[],[4902],{"type":420,"value":4903},"httpclient-vault",{"type":420,"value":4905}," and set 3 secrets in it.",{"type":415,"tag":416,"props":4907,"children":4908},{},[4909],{"type":415,"tag":1322,"props":4910,"children":4914},{"alt":4911,"className":4912,"src":4913},"The Secrets view of an Azure Key Vault resource in Azure Portal",[1326,1327],"/posts/images/httpclientssecrets_keyvault_1.png",[],{"type":415,"tag":416,"props":4916,"children":4917},{},[4918,4920,4925],{"type":420,"value":4919},"What I am trying to achieve is to produce the following file ",{"type":415,"tag":550,"props":4921,"children":4923},{"className":4922},[],[4924],{"type":420,"value":4805},{"type":420,"value":635},{"type":415,"tag":773,"props":4927,"children":4929},{"className":775,"code":4928,"language":777,"meta":401,"style":401},"{\n  \"development\":\n  {\n    \"ApiKey\": \"12345678\",\n    \"Username\": \"admin\",\n    \"UserPassword\": \"Password\"\n  }\n}\n",[4930],{"type":415,"tag":550,"props":4931,"children":4932},{"__ignoreMap":401},[4933,4940,4961,4969,5006,5043,5076,5083],{"type":415,"tag":783,"props":4934,"children":4935},{"class":785,"line":786},[4936],{"type":415,"tag":783,"props":4937,"children":4938},{"style":790},[4939],{"type":420,"value":793},{"type":415,"tag":783,"props":4941,"children":4942},{"class":785,"line":796},[4943,4947,4952,4956],{"type":415,"tag":783,"props":4944,"children":4945},{"style":790},[4946],{"type":420,"value":802},{"type":415,"tag":783,"props":4948,"children":4949},{"style":805},[4950],{"type":420,"value":4951},"development",{"type":415,"tag":783,"props":4953,"children":4954},{"style":790},[4955],{"type":420,"value":813},{"type":415,"tag":783,"props":4957,"children":4958},{"style":790},[4959],{"type":420,"value":4960},":\n",{"type":415,"tag":783,"props":4962,"children":4963},{"class":785,"line":840},[4964],{"type":415,"tag":783,"props":4965,"children":4966},{"style":790},[4967],{"type":420,"value":4968},"  {\n",{"type":415,"tag":783,"props":4970,"children":4971},{"class":785,"line":866},[4972,4976,4981,4985,4989,4993,4998,5002],{"type":415,"tag":783,"props":4973,"children":4974},{"style":790},[4975],{"type":420,"value":872},{"type":415,"tag":783,"props":4977,"children":4978},{"style":875},[4979],{"type":420,"value":4980},"ApiKey",{"type":415,"tag":783,"props":4982,"children":4983},{"style":790},[4984],{"type":420,"value":813},{"type":415,"tag":783,"props":4986,"children":4987},{"style":790},[4988],{"type":420,"value":635},{"type":415,"tag":783,"props":4990,"children":4991},{"style":790},[4992],{"type":420,"value":822},{"type":415,"tag":783,"props":4994,"children":4995},{"style":825},[4996],{"type":420,"value":4997},"12345678",{"type":415,"tag":783,"props":4999,"children":5000},{"style":790},[5001],{"type":420,"value":813},{"type":415,"tag":783,"props":5003,"children":5004},{"style":790},[5005],{"type":420,"value":837},{"type":415,"tag":783,"props":5007,"children":5008},{"class":785,"line":893},[5009,5013,5018,5022,5026,5030,5035,5039],{"type":415,"tag":783,"props":5010,"children":5011},{"style":790},[5012],{"type":420,"value":872},{"type":415,"tag":783,"props":5014,"children":5015},{"style":875},[5016],{"type":420,"value":5017},"Username",{"type":415,"tag":783,"props":5019,"children":5020},{"style":790},[5021],{"type":420,"value":813},{"type":415,"tag":783,"props":5023,"children":5024},{"style":790},[5025],{"type":420,"value":635},{"type":415,"tag":783,"props":5027,"children":5028},{"style":790},[5029],{"type":420,"value":822},{"type":415,"tag":783,"props":5031,"children":5032},{"style":825},[5033],{"type":420,"value":5034},"admin",{"type":415,"tag":783,"props":5036,"children":5037},{"style":790},[5038],{"type":420,"value":813},{"type":415,"tag":783,"props":5040,"children":5041},{"style":790},[5042],{"type":420,"value":837},{"type":415,"tag":783,"props":5044,"children":5045},{"class":785,"line":920},[5046,5050,5055,5059,5063,5067,5072],{"type":415,"tag":783,"props":5047,"children":5048},{"style":790},[5049],{"type":420,"value":872},{"type":415,"tag":783,"props":5051,"children":5052},{"style":875},[5053],{"type":420,"value":5054},"UserPassword",{"type":415,"tag":783,"props":5056,"children":5057},{"style":790},[5058],{"type":420,"value":813},{"type":415,"tag":783,"props":5060,"children":5061},{"style":790},[5062],{"type":420,"value":635},{"type":415,"tag":783,"props":5064,"children":5065},{"style":790},[5066],{"type":420,"value":822},{"type":415,"tag":783,"props":5068,"children":5069},{"style":825},[5070],{"type":420,"value":5071},"Password",{"type":415,"tag":783,"props":5073,"children":5074},{"style":790},[5075],{"type":420,"value":3385},{"type":415,"tag":783,"props":5077,"children":5078},{"class":785,"line":959},[5079],{"type":415,"tag":783,"props":5080,"children":5081},{"style":790},[5082],{"type":420,"value":1263},{"type":415,"tag":783,"props":5084,"children":5085},{"class":785,"line":997},[5086],{"type":415,"tag":783,"props":5087,"children":5088},{"style":790},[5089],{"type":420,"value":1272},{"type":415,"tag":416,"props":5091,"children":5092},{},[5093],{"type":420,"value":5094},"First, let's list the secrets in the Key Vault:",{"type":415,"tag":416,"props":5096,"children":5097},{},[5098],{"type":415,"tag":1322,"props":5099,"children":5103},{"alt":5100,"className":5101,"src":5102},"An Azure CLI command that lists Key Vault secrets in terminal.",[1326,1327],"/posts/images/httpclientssecrets_script_1.png",[],{"type":415,"tag":416,"props":5105,"children":5106},{},[5107],{"type":420,"value":5108},"The output of the command is not that easy to read because it's JSON and there are some properties we are not interested in. However, Azure CLI supports different output formats and can be used with JMESPath expressions to query the output of a command like this:",{"type":415,"tag":416,"props":5110,"children":5111},{},[5112],{"type":415,"tag":1322,"props":5113,"children":5117},{"alt":5114,"className":5115,"src":5116},"An Azure CLI command using JMESPath that lists Key Vault secrets in terminal.",[1326,1327],"/posts/images/httpclientssecrets_script_2.png",[],{"type":415,"tag":416,"props":5119,"children":5120},{},[5121,5123,5129],{"type":420,"value":5122},"It's nice but I won't need to use this because I can use Nushell (aka Nu) pipelines where everything is structured data that can be filtered, selected, and sorted. To bring the Azure CLI command output into a Nu pipeline, I can use the ",{"type":415,"tag":550,"props":5124,"children":5126},{"className":5125},[],[5127],{"type":420,"value":5128},"from json",{"type":420,"value":1864},{"type":415,"tag":416,"props":5131,"children":5132},{},[5133],{"type":415,"tag":1322,"props":5134,"children":5138},{"alt":5135,"className":5136,"src":5137},"The output of the \"az keyvault secret list --vault-name httpclient-vault | from json\" command in terminal.",[1326,1327],"/posts/images/httpclientssecrets_script_3.png",[],{"type":415,"tag":588,"props":5140,"children":5141},{"icon":728},[5142],{"type":415,"tag":416,"props":5143,"children":5144},{},[5145,5147,5153],{"type":420,"value":5146},"Nu has many ",{"type":415,"tag":550,"props":5148,"children":5150},{"className":5149},[],[5151],{"type":420,"value":5152},"from",{"type":420,"value":5154}," commands to convert data from different formats to structured data/table.",{"type":415,"tag":416,"props":5156,"children":5157},{},[5158,5160,5166],{"type":420,"value":5159},"You probably have noticed that the Azure CLI command we used to list the secrets does not provide their values. To retrieve the secret values we have to call another command for each secret using the id of the secret like this: ",{"type":415,"tag":550,"props":5161,"children":5163},{"className":5162},[],[5164],{"type":420,"value":5165},"az keyvault secret show --id $secretId",{"type":420,"value":755},{"type":415,"tag":416,"props":5168,"children":5169},{},[5170],{"type":415,"tag":1322,"props":5171,"children":5175},{"alt":5172,"className":5173,"src":5174},"An Azure CLI command that get a secret from Key Vault in terminal.",[1326,1327],"/posts/images/httpclientssecrets_script_4.png",[],{"type":415,"tag":416,"props":5177,"children":5178},{},[5179,5181,5186,5188,5194],{"type":420,"value":5180},"Again we can use the ",{"type":415,"tag":550,"props":5182,"children":5184},{"className":5183},[],[5185],{"type":420,"value":5128},{"type":420,"value":5187}," command, and the ",{"type":415,"tag":550,"props":5189,"children":5191},{"className":5190},[],[5192],{"type":420,"value":5193},"get",{"type":420,"value":5195}," command to only retrieve the value of a secret.",{"type":415,"tag":416,"props":5197,"children":5198},{},[5199],{"type":415,"tag":1322,"props":5200,"children":5204},{"alt":5201,"className":5202,"src":5203},"The output of the nushell script retrieving a secret value from keyvault.",[1326,1327],"/posts/images/httpclientssecrets_script_5.png",[],{"type":415,"tag":416,"props":5206,"children":5207},{},[5208,5210,5216],{"type":420,"value":5209},"Now that we know how to retrieve the value of a secret, we can insert a new column ",{"type":415,"tag":550,"props":5211,"children":5213},{"className":5212},[],[5214],{"type":420,"value":5215},"value",{"type":420,"value":5217}," into our table that will be filled with the value of each secret retrieved   using the previous command:",{"type":415,"tag":416,"props":5219,"children":5220},{},[5221],{"type":415,"tag":1322,"props":5222,"children":5226},{"alt":5223,"className":5224,"src":5225},"The output of the nushell script retrieving a list of secrets from keyvault.",[1326,1327],"/posts/images/httpclientssecrets_script_6.png",[],{"type":415,"tag":416,"props":5228,"children":5229},{},[5230,5231,5237,5239,5245,5247,5253],{"type":420,"value":3261},{"type":415,"tag":550,"props":5232,"children":5234},{"className":5233},[],[5235],{"type":420,"value":5236},"{|secret| (az keyvault secret show --id $secret.id | from json | get value)}",{"type":420,"value":5238}," part is a block that is executed for each row. The ",{"type":415,"tag":550,"props":5240,"children":5242},{"className":5241},[],[5243],{"type":420,"value":5244},"secret",{"type":420,"value":5246}," is the parameter of the block which represents the row, with the values of the columns for this row being available as properties of the variable ",{"type":415,"tag":550,"props":5248,"children":5250},{"className":5249},[],[5251],{"type":420,"value":5252},"$secret",{"type":420,"value":5254},". As the command was becoming long for a single line, we wrapped it in parentheses that allow us to write the command on multiple lines.",{"type":415,"tag":416,"props":5256,"children":5257},{},[5258],{"type":420,"value":5259},"As we are only interested in the columns \"name\" and \"value\", we only select them.",{"type":415,"tag":416,"props":5261,"children":5262},{},[5263],{"type":415,"tag":1322,"props":5264,"children":5268},{"alt":5265,"className":5266,"src":5267},"The output of the nushell script retrieving Azure Key Vault secrets (name and value).",[1326,1327],"/posts/images/httpclientssecrets_script_7.png",[],{"type":415,"tag":416,"props":5270,"children":5271},{},[5272,5274,5280],{"type":420,"value":5273},"We have to reorganize the data to make key-value pairs where keys come from the column name and values from the column value. We can use the ",{"type":415,"tag":550,"props":5275,"children":5277},{"className":5276},[],[5278],{"type":420,"value":5279},"transpose",{"type":420,"value":5281}," with the proper flags to do that:",{"type":415,"tag":416,"props":5283,"children":5284},{},[5285],{"type":415,"tag":1322,"props":5286,"children":5289},{"alt":5265,"className":5287,"src":5288},[1326,1327],"/posts/images/httpclientssecrets_script_8.png",[],{"type":415,"tag":416,"props":5291,"children":5292},{},[5293],{"type":420,"value":5294},"Then we wrap the key-value pairs in a JSON object corresponding to the development environment:",{"type":415,"tag":416,"props":5296,"children":5297},{},[5298],{"type":415,"tag":1322,"props":5299,"children":5303},{"alt":5300,"className":5301,"src":5302},"The output of the nushell script creating a JSON object from Azure Key Vault secrets.",[1326,1327],"/posts/images/httpclientssecrets_script_9.png",[],{"type":415,"tag":416,"props":5305,"children":5306},{},[5307,5309,5315],{"type":420,"value":5308},"We can check we get the JSON we want with the ",{"type":415,"tag":550,"props":5310,"children":5312},{"className":5311},[],[5313],{"type":420,"value":5314},"to json",{"type":420,"value":1864},{"type":415,"tag":416,"props":5317,"children":5318},{},[5319],{"type":415,"tag":1322,"props":5320,"children":5324},{"alt":5321,"className":5322,"src":5323},"The output of the nushell script creating a JSON string from Azure Key Vault secrets.",[1326,1327],"/posts/images/httpclientssecrets_script_10.png",[],{"type":415,"tag":416,"props":5326,"children":5327},{},[5328,5330,5335],{"type":420,"value":5329},"And finally, we can save the data in a ",{"type":415,"tag":550,"props":5331,"children":5333},{"className":5332},[],[5334],{"type":420,"value":4805},{"type":420,"value":5336}," file using the save command.",{"type":415,"tag":416,"props":5338,"children":5339},{},[5340],{"type":420,"value":5341},"Here is the final script 🔽:",{"type":415,"tag":773,"props":5343,"children":5346},{"className":5344,"code":5345,"language":243,"meta":401,"style":401},"language-nushell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","(\n  az keyvault secret list --vault-name httpclient-vault |\n  from json |\n  insert value {|secret| (az keyvault secret show --id $secret.id | from json | get value)} |\n  select name value |\n  transpose -rd |\n  { development: $in } |\n  save http-client.private.env.json\n)\n",[5347],{"type":415,"tag":550,"props":5348,"children":5349},{"__ignoreMap":401},[5350,5358,5402,5414,5513,5534,5555,5585,5598],{"type":415,"tag":783,"props":5351,"children":5352},{"class":785,"line":786},[5353],{"type":415,"tag":783,"props":5354,"children":5355},{"style":1362},[5356],{"type":420,"value":5357},"(\n",{"type":415,"tag":783,"props":5359,"children":5360},{"class":785,"line":796},[5361,5366,5371,5376,5381,5386,5392,5397],{"type":415,"tag":783,"props":5362,"children":5363},{"style":875},[5364],{"type":420,"value":5365},"  az",{"type":415,"tag":783,"props":5367,"children":5368},{"style":825},[5369],{"type":420,"value":5370}," keyvault",{"type":415,"tag":783,"props":5372,"children":5373},{"style":825},[5374],{"type":420,"value":5375}," secret",{"type":415,"tag":783,"props":5377,"children":5378},{"style":825},[5379],{"type":420,"value":5380}," list",{"type":415,"tag":783,"props":5382,"children":5383},{"style":1356},[5384],{"type":420,"value":5385}," --",{"type":415,"tag":783,"props":5387,"children":5389},{"style":5388},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8;--shiki-light-font-style:italic;--shiki-default-font-style:italic;--shiki-dark-font-style:italic",[5390],{"type":420,"value":5391},"vault-name",{"type":415,"tag":783,"props":5393,"children":5394},{"style":825},[5395],{"type":420,"value":5396}," httpclient-vault",{"type":415,"tag":783,"props":5398,"children":5399},{"style":1356},[5400],{"type":420,"value":5401}," |\n",{"type":415,"tag":783,"props":5403,"children":5404},{"class":785,"line":840},[5405,5410],{"type":415,"tag":783,"props":5406,"children":5407},{"style":902},[5408],{"type":420,"value":5409},"  from json",{"type":415,"tag":783,"props":5411,"children":5412},{"style":1356},[5413],{"type":420,"value":5401},{"type":415,"tag":783,"props":5415,"children":5416},{"class":785,"line":866},[5417,5422,5427,5432,5436,5440,5445,5450,5454,5458,5463,5467,5472,5477,5481,5486,5491,5496,5500,5504,5509],{"type":415,"tag":783,"props":5418,"children":5419},{"style":902},[5420],{"type":420,"value":5421},"  insert",{"type":415,"tag":783,"props":5423,"children":5424},{"style":825},[5425],{"type":420,"value":5426}," value",{"type":415,"tag":783,"props":5428,"children":5429},{"style":790},[5430],{"type":420,"value":5431}," {",{"type":415,"tag":783,"props":5433,"children":5434},{"style":1362},[5435],{"type":420,"value":4216},{"type":415,"tag":783,"props":5437,"children":5438},{"style":5388},[5439],{"type":420,"value":5244},{"type":415,"tag":783,"props":5441,"children":5442},{"style":1362},[5443],{"type":420,"value":5444},"| (",{"type":415,"tag":783,"props":5446,"children":5447},{"style":875},[5448],{"type":420,"value":5449},"az",{"type":415,"tag":783,"props":5451,"children":5452},{"style":825},[5453],{"type":420,"value":5370},{"type":415,"tag":783,"props":5455,"children":5456},{"style":825},[5457],{"type":420,"value":5375},{"type":415,"tag":783,"props":5459,"children":5460},{"style":825},[5461],{"type":420,"value":5462}," show",{"type":415,"tag":783,"props":5464,"children":5465},{"style":1356},[5466],{"type":420,"value":5385},{"type":415,"tag":783,"props":5468,"children":5469},{"style":5388},[5470],{"type":420,"value":5471},"id",{"type":415,"tag":783,"props":5473,"children":5474},{"style":1362},[5475],{"type":420,"value":5476}," $secret.id ",{"type":415,"tag":783,"props":5478,"children":5479},{"style":1356},[5480],{"type":420,"value":4216},{"type":415,"tag":783,"props":5482,"children":5483},{"style":902},[5484],{"type":420,"value":5485}," from json",{"type":415,"tag":783,"props":5487,"children":5488},{"style":1356},[5489],{"type":420,"value":5490}," |",{"type":415,"tag":783,"props":5492,"children":5493},{"style":902},[5494],{"type":420,"value":5495}," get",{"type":415,"tag":783,"props":5497,"children":5498},{"style":825},[5499],{"type":420,"value":5426},{"type":415,"tag":783,"props":5501,"children":5502},{"style":1362},[5503],{"type":420,"value":2148},{"type":415,"tag":783,"props":5505,"children":5506},{"style":790},[5507],{"type":420,"value":5508},"}",{"type":415,"tag":783,"props":5510,"children":5511},{"style":1356},[5512],{"type":420,"value":5401},{"type":415,"tag":783,"props":5514,"children":5515},{"class":785,"line":893},[5516,5521,5526,5530],{"type":415,"tag":783,"props":5517,"children":5518},{"style":902},[5519],{"type":420,"value":5520},"  select",{"type":415,"tag":783,"props":5522,"children":5523},{"style":825},[5524],{"type":420,"value":5525}," name",{"type":415,"tag":783,"props":5527,"children":5528},{"style":825},[5529],{"type":420,"value":5426},{"type":415,"tag":783,"props":5531,"children":5532},{"style":1356},[5533],{"type":420,"value":5401},{"type":415,"tag":783,"props":5535,"children":5536},{"class":785,"line":920},[5537,5542,5546,5551],{"type":415,"tag":783,"props":5538,"children":5539},{"style":902},[5540],{"type":420,"value":5541},"  transpose",{"type":415,"tag":783,"props":5543,"children":5544},{"style":1356},[5545],{"type":420,"value":4234},{"type":415,"tag":783,"props":5547,"children":5548},{"style":5388},[5549],{"type":420,"value":5550},"rd",{"type":415,"tag":783,"props":5552,"children":5553},{"style":1356},[5554],{"type":420,"value":5401},{"type":415,"tag":783,"props":5556,"children":5557},{"class":785,"line":959},[5558,5563,5568,5572,5577,5581],{"type":415,"tag":783,"props":5559,"children":5560},{"style":790},[5561],{"type":420,"value":5562},"  {",{"type":415,"tag":783,"props":5564,"children":5565},{"style":1362},[5566],{"type":420,"value":5567}," development",{"type":415,"tag":783,"props":5569,"children":5570},{"style":1356},[5571],{"type":420,"value":635},{"type":415,"tag":783,"props":5573,"children":5574},{"style":1362},[5575],{"type":420,"value":5576}," $in ",{"type":415,"tag":783,"props":5578,"children":5579},{"style":790},[5580],{"type":420,"value":5508},{"type":415,"tag":783,"props":5582,"children":5583},{"style":1356},[5584],{"type":420,"value":5401},{"type":415,"tag":783,"props":5586,"children":5587},{"class":785,"line":997},[5588,5593],{"type":415,"tag":783,"props":5589,"children":5590},{"style":902},[5591],{"type":420,"value":5592},"  save",{"type":415,"tag":783,"props":5594,"children":5595},{"style":825},[5596],{"type":420,"value":5597}," http-client.private.env.json\n",{"type":415,"tag":783,"props":5599,"children":5600},{"class":785,"line":1023},[5601],{"type":415,"tag":783,"props":5602,"children":5603},{"style":1362},[5604],{"type":420,"value":4154},{"type":415,"tag":443,"props":5606,"children":5607},{"id":2942},[5608],{"type":420,"value":2945},{"type":415,"tag":416,"props":5610,"children":5611},{},[5612],{"type":420,"value":5613},"In this example, I scripted with Nu the retrieval of secrets from an Azure Key Vault, but it should not be too difficult to apply the same concepts to fetch secrets from another vault.",{"type":415,"tag":416,"props":5615,"children":5616},{},[5617,5619,5626],{"type":420,"value":5618},"I had fun playing with Azure CLI and Nushell to write this script but there are many other ways to do the same thing. There are also probably other tools or services (I have just came across ",{"type":415,"tag":423,"props":5620,"children":5623},{"href":5621,"rel":5622},"https://www.doppler.com/",[427],[5624],{"type":420,"value":5625},"Doppler",{"type":420,"value":5627}," which seems nice) that can help you manage secrets securely.",{"type":415,"tag":416,"props":5629,"children":5630},{},[5631],{"type":420,"value":5632},"I am not a Nushell expert but I find it awesome, and am considering making it my main shell. You should give it a try too. A big thank you to the people in the Nushell Discord that help me with my script ❤️.",{"type":415,"tag":1420,"props":5634,"children":5635},{},[5636],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":5638},[5639,5640,5641,5642],{"id":4682,"depth":796,"text":4685},{"id":4845,"depth":796,"text":4848},{"id":4869,"depth":796,"text":4872},{"id":2942,"depth":796,"text":2945},"content:1.posts:39.http-clients-secrets.md","1.posts/39.http-clients-secrets.md",{"_path":121,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":120,"description":5646,"lead":5646,"date":5647,"image":5648,"badge":5649,"tags":5650,"body":5651,"_type":1435,"_id":6823,"_source":1437,"_file":6824,"_extension":1439},"The \"this\" in TypeScript, a must-have tool for Durable Functions, and a new git alias.","2022-05-23T00:00:00.000Z",{"src":2208},{"label":2210},[272,358,241,257,206],{"type":412,"children":5652,"toc":6817},[5653,5665,5686,5691,5919,5953,5979,6086,6095,6115,6228,6260,6662,6668,6682,6691,6703,6711,6717,6731,6774,6809,6813],{"type":415,"tag":443,"props":5654,"children":5656},{"id":5655},"reminder-for-my-future-self-dont-forget-about-this-in-ts",[5657,5659],{"type":420,"value":5658},"Reminder for my future self: don't forget about \"this\" in ",{"type":415,"tag":550,"props":5660,"children":5662},{"className":5661},[],[5663],{"type":420,"value":5664},"ts",{"type":415,"tag":416,"props":5666,"children":5667},{},[5668,5670,5676,5678,5684],{"type":420,"value":5669},"Once again, I found myself forgetting that ",{"type":415,"tag":550,"props":5671,"children":5673},{"className":5672},[],[5674],{"type":420,"value":5675},"this",{"type":420,"value":5677}," can lose context in JavaScript/TypeScript which results in exceptions because ",{"type":415,"tag":550,"props":5679,"children":5681},{"className":5680},[],[5682],{"type":420,"value":5683},"this ",{"type":420,"value":5685}," is undefined. This is probably obvious for most developers but this is not a case I come across often so it's better to write it down so that I have something to refer to next time.",{"type":415,"tag":416,"props":5687,"children":5688},{},[5689],{"type":420,"value":5690},"Let's take an example:",{"type":415,"tag":773,"props":5692,"children":5695},{"className":5693,"code":5694,"language":5664,"meta":401,"style":401},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","class AudioBook {\n    private isStarted = false;\n\n    constructor(public title: string) {\n    }\n    \n    play() {\n        this.isStarted = true;\n    }\n\n    stop() {\n        this.isStarted = false;\n    }\n}\n",[5696],{"type":415,"tag":550,"props":5697,"children":5698},{"__ignoreMap":401},[5699,5716,5745,5754,5794,5801,5809,5826,5852,5859,5866,5882,5905,5912],{"type":415,"tag":783,"props":5700,"children":5701},{"class":785,"line":786},[5702,5707,5712],{"type":415,"tag":783,"props":5703,"children":5704},{"style":805},[5705],{"type":420,"value":5706},"class",{"type":415,"tag":783,"props":5708,"children":5709},{"style":875},[5710],{"type":420,"value":5711}," AudioBook",{"type":415,"tag":783,"props":5713,"children":5714},{"style":790},[5715],{"type":420,"value":863},{"type":415,"tag":783,"props":5717,"children":5718},{"class":785,"line":796},[5719,5724,5729,5734,5740],{"type":415,"tag":783,"props":5720,"children":5721},{"style":805},[5722],{"type":420,"value":5723},"    private",{"type":415,"tag":783,"props":5725,"children":5726},{"style":929},[5727],{"type":420,"value":5728}," isStarted",{"type":415,"tag":783,"props":5730,"children":5731},{"style":790},[5732],{"type":420,"value":5733}," =",{"type":415,"tag":783,"props":5735,"children":5737},{"style":5736},"--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC",[5738],{"type":420,"value":5739}," false",{"type":415,"tag":783,"props":5741,"children":5742},{"style":790},[5743],{"type":420,"value":5744},";\n",{"type":415,"tag":783,"props":5746,"children":5747},{"class":785,"line":840},[5748],{"type":415,"tag":783,"props":5749,"children":5751},{"emptyLinePlaceholder":5750},true,[5752],{"type":420,"value":5753},"\n",{"type":415,"tag":783,"props":5755,"children":5756},{"class":785,"line":866},[5757,5762,5767,5772,5777,5781,5786,5790],{"type":415,"tag":783,"props":5758,"children":5759},{"style":805},[5760],{"type":420,"value":5761},"    constructor",{"type":415,"tag":783,"props":5763,"children":5764},{"style":790},[5765],{"type":420,"value":5766},"(",{"type":415,"tag":783,"props":5768,"children":5769},{"style":805},[5770],{"type":420,"value":5771},"public",{"type":415,"tag":783,"props":5773,"children":5774},{"style":5388},[5775],{"type":420,"value":5776}," title",{"type":415,"tag":783,"props":5778,"children":5779},{"style":790},[5780],{"type":420,"value":635},{"type":415,"tag":783,"props":5782,"children":5783},{"style":875},[5784],{"type":420,"value":5785}," string",{"type":415,"tag":783,"props":5787,"children":5788},{"style":790},[5789],{"type":420,"value":2148},{"type":415,"tag":783,"props":5791,"children":5792},{"style":790},[5793],{"type":420,"value":863},{"type":415,"tag":783,"props":5795,"children":5796},{"class":785,"line":893},[5797],{"type":415,"tag":783,"props":5798,"children":5799},{"style":790},[5800],{"type":420,"value":1254},{"type":415,"tag":783,"props":5802,"children":5803},{"class":785,"line":920},[5804],{"type":415,"tag":783,"props":5805,"children":5806},{"style":1362},[5807],{"type":420,"value":5808},"    \n",{"type":415,"tag":783,"props":5810,"children":5811},{"class":785,"line":959},[5812,5817,5822],{"type":415,"tag":783,"props":5813,"children":5814},{"style":929},[5815],{"type":420,"value":5816},"    play",{"type":415,"tag":783,"props":5818,"children":5819},{"style":790},[5820],{"type":420,"value":5821},"()",{"type":415,"tag":783,"props":5823,"children":5824},{"style":790},[5825],{"type":420,"value":863},{"type":415,"tag":783,"props":5827,"children":5828},{"class":785,"line":997},[5829,5834,5839,5843,5848],{"type":415,"tag":783,"props":5830,"children":5831},{"style":790},[5832],{"type":420,"value":5833},"        this.",{"type":415,"tag":783,"props":5835,"children":5836},{"style":1362},[5837],{"type":420,"value":5838},"isStarted",{"type":415,"tag":783,"props":5840,"children":5841},{"style":790},[5842],{"type":420,"value":5733},{"type":415,"tag":783,"props":5844,"children":5845},{"style":5736},[5846],{"type":420,"value":5847}," true",{"type":415,"tag":783,"props":5849,"children":5850},{"style":790},[5851],{"type":420,"value":5744},{"type":415,"tag":783,"props":5853,"children":5854},{"class":785,"line":1023},[5855],{"type":415,"tag":783,"props":5856,"children":5857},{"style":790},[5858],{"type":420,"value":1254},{"type":415,"tag":783,"props":5860,"children":5861},{"class":785,"line":1061},[5862],{"type":415,"tag":783,"props":5863,"children":5864},{"emptyLinePlaceholder":5750},[5865],{"type":420,"value":5753},{"type":415,"tag":783,"props":5867,"children":5868},{"class":785,"line":1099},[5869,5874,5878],{"type":415,"tag":783,"props":5870,"children":5871},{"style":929},[5872],{"type":420,"value":5873},"    stop",{"type":415,"tag":783,"props":5875,"children":5876},{"style":790},[5877],{"type":420,"value":5821},{"type":415,"tag":783,"props":5879,"children":5880},{"style":790},[5881],{"type":420,"value":863},{"type":415,"tag":783,"props":5883,"children":5884},{"class":785,"line":1137},[5885,5889,5893,5897,5901],{"type":415,"tag":783,"props":5886,"children":5887},{"style":790},[5888],{"type":420,"value":5833},{"type":415,"tag":783,"props":5890,"children":5891},{"style":1362},[5892],{"type":420,"value":5838},{"type":415,"tag":783,"props":5894,"children":5895},{"style":790},[5896],{"type":420,"value":5733},{"type":415,"tag":783,"props":5898,"children":5899},{"style":5736},[5900],{"type":420,"value":5739},{"type":415,"tag":783,"props":5902,"children":5903},{"style":790},[5904],{"type":420,"value":5744},{"type":415,"tag":783,"props":5906,"children":5907},{"class":785,"line":1175},[5908],{"type":415,"tag":783,"props":5909,"children":5910},{"style":790},[5911],{"type":420,"value":1254},{"type":415,"tag":783,"props":5913,"children":5914},{"class":785,"line":1213},[5915],{"type":415,"tag":783,"props":5916,"children":5917},{"style":790},[5918],{"type":420,"value":1272},{"type":415,"tag":416,"props":5920,"children":5921},{},[5922,5924,5930,5932,5937,5939,5945,5946,5952],{"type":420,"value":5923},"This is a class ",{"type":415,"tag":550,"props":5925,"children":5927},{"className":5926},[],[5928],{"type":420,"value":5929},"AudioBook",{"type":420,"value":5931}," that has a private boolean field ",{"type":415,"tag":550,"props":5933,"children":5935},{"className":5934},[],[5936],{"type":420,"value":5838},{"type":420,"value":5938}," that is modified by the two methods ",{"type":415,"tag":550,"props":5940,"children":5942},{"className":5941},[],[5943],{"type":420,"value":5944},"play",{"type":420,"value":3487},{"type":415,"tag":550,"props":5947,"children":5949},{"className":5948},[],[5950],{"type":420,"value":5951},"stop",{"type":420,"value":755},{"type":415,"tag":416,"props":5954,"children":5955},{},[5956,5958,5963,5965,5970,5972,5977],{"type":420,"value":5957},"If I create an instance of ",{"type":415,"tag":550,"props":5959,"children":5961},{"className":5960},[],[5962],{"type":420,"value":5929},{"type":420,"value":5964}," and I want to assign the ",{"type":415,"tag":550,"props":5966,"children":5968},{"className":5967},[],[5969],{"type":420,"value":5944},{"type":420,"value":5971}," function to a variable, an exception will occur when the function is run because ",{"type":415,"tag":550,"props":5973,"children":5975},{"className":5974},[],[5976],{"type":420,"value":5675},{"type":420,"value":5978}," will be undefined.",{"type":415,"tag":773,"props":5980,"children":5982},{"className":5693,"code":5981,"language":5664,"meta":401,"style":401},"const audioBook = new AudioBook(\"The Unicorn Project\");\nconst listenDevOpsBook = audioBook.play;\nlistenDevOpsBook();\n",[5983],{"type":415,"tag":550,"props":5984,"children":5985},{"__ignoreMap":401},[5986,6037,6070],{"type":415,"tag":783,"props":5987,"children":5988},{"class":785,"line":786},[5989,5994,5999,6003,6008,6012,6016,6020,6025,6029,6033],{"type":415,"tag":783,"props":5990,"children":5991},{"style":805},[5992],{"type":420,"value":5993},"const",{"type":415,"tag":783,"props":5995,"children":5996},{"style":1362},[5997],{"type":420,"value":5998}," audioBook ",{"type":415,"tag":783,"props":6000,"children":6001},{"style":790},[6002],{"type":420,"value":1993},{"type":415,"tag":783,"props":6004,"children":6005},{"style":790},[6006],{"type":420,"value":6007}," new",{"type":415,"tag":783,"props":6009,"children":6010},{"style":3409},[6011],{"type":420,"value":5711},{"type":415,"tag":783,"props":6013,"children":6014},{"style":1362},[6015],{"type":420,"value":5766},{"type":415,"tag":783,"props":6017,"children":6018},{"style":790},[6019],{"type":420,"value":813},{"type":415,"tag":783,"props":6021,"children":6022},{"style":825},[6023],{"type":420,"value":6024},"The Unicorn Project",{"type":415,"tag":783,"props":6026,"children":6027},{"style":790},[6028],{"type":420,"value":813},{"type":415,"tag":783,"props":6030,"children":6031},{"style":1362},[6032],{"type":420,"value":2148},{"type":415,"tag":783,"props":6034,"children":6035},{"style":790},[6036],{"type":420,"value":5744},{"type":415,"tag":783,"props":6038,"children":6039},{"class":785,"line":796},[6040,6044,6049,6053,6058,6062,6066],{"type":415,"tag":783,"props":6041,"children":6042},{"style":805},[6043],{"type":420,"value":5993},{"type":415,"tag":783,"props":6045,"children":6046},{"style":1362},[6047],{"type":420,"value":6048}," listenDevOpsBook ",{"type":415,"tag":783,"props":6050,"children":6051},{"style":790},[6052],{"type":420,"value":1993},{"type":415,"tag":783,"props":6054,"children":6055},{"style":1362},[6056],{"type":420,"value":6057}," audioBook",{"type":415,"tag":783,"props":6059,"children":6060},{"style":790},[6061],{"type":420,"value":755},{"type":415,"tag":783,"props":6063,"children":6064},{"style":1362},[6065],{"type":420,"value":5944},{"type":415,"tag":783,"props":6067,"children":6068},{"style":790},[6069],{"type":420,"value":5744},{"type":415,"tag":783,"props":6071,"children":6072},{"class":785,"line":840},[6073,6078,6082],{"type":415,"tag":783,"props":6074,"children":6075},{"style":3409},[6076],{"type":420,"value":6077},"listenDevOpsBook",{"type":415,"tag":783,"props":6079,"children":6080},{"style":1362},[6081],{"type":420,"value":5821},{"type":415,"tag":783,"props":6083,"children":6084},{"style":790},[6085],{"type":420,"value":5744},{"type":415,"tag":416,"props":6087,"children":6088},{},[6089],{"type":415,"tag":1322,"props":6090,"children":6094},{"alt":6091,"className":6092,"src":6093},"JavaScript Failed error in console output.",[1326,1327],"/posts/images/w202022tips_this_1.png",[],{"type":415,"tag":416,"props":6096,"children":6097},{},[6098,6100,6107,6109,6114],{"type":420,"value":6099},"The solution to avoid that is to use the ",{"type":415,"tag":423,"props":6101,"children":6104},{"href":6102,"rel":6103},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind",[427],[6105],{"type":420,"value":6106},"bind",{"type":420,"value":6108}," function to specify the object to use as ",{"type":415,"tag":550,"props":6110,"children":6112},{"className":6111},[],[6113],{"type":420,"value":5675},{"type":420,"value":755},{"type":415,"tag":773,"props":6116,"children":6118},{"className":5693,"code":6117,"language":5664,"meta":401,"style":401},"const audioBook = new AudioBook(\"The Unicorn Project\");\nconst listenDevOpsBook = audioBook.play.bind(audioBook);\nlistenDevOpsBook();\n",[6119],{"type":415,"tag":550,"props":6120,"children":6121},{"__ignoreMap":401},[6122,6169,6213],{"type":415,"tag":783,"props":6123,"children":6124},{"class":785,"line":786},[6125,6129,6133,6137,6141,6145,6149,6153,6157,6161,6165],{"type":415,"tag":783,"props":6126,"children":6127},{"style":805},[6128],{"type":420,"value":5993},{"type":415,"tag":783,"props":6130,"children":6131},{"style":1362},[6132],{"type":420,"value":5998},{"type":415,"tag":783,"props":6134,"children":6135},{"style":790},[6136],{"type":420,"value":1993},{"type":415,"tag":783,"props":6138,"children":6139},{"style":790},[6140],{"type":420,"value":6007},{"type":415,"tag":783,"props":6142,"children":6143},{"style":3409},[6144],{"type":420,"value":5711},{"type":415,"tag":783,"props":6146,"children":6147},{"style":1362},[6148],{"type":420,"value":5766},{"type":415,"tag":783,"props":6150,"children":6151},{"style":790},[6152],{"type":420,"value":813},{"type":415,"tag":783,"props":6154,"children":6155},{"style":825},[6156],{"type":420,"value":6024},{"type":415,"tag":783,"props":6158,"children":6159},{"style":790},[6160],{"type":420,"value":813},{"type":415,"tag":783,"props":6162,"children":6163},{"style":1362},[6164],{"type":420,"value":2148},{"type":415,"tag":783,"props":6166,"children":6167},{"style":790},[6168],{"type":420,"value":5744},{"type":415,"tag":783,"props":6170,"children":6171},{"class":785,"line":796},[6172,6176,6180,6184,6188,6192,6196,6200,6204,6209],{"type":415,"tag":783,"props":6173,"children":6174},{"style":805},[6175],{"type":420,"value":5993},{"type":415,"tag":783,"props":6177,"children":6178},{"style":1362},[6179],{"type":420,"value":6048},{"type":415,"tag":783,"props":6181,"children":6182},{"style":790},[6183],{"type":420,"value":1993},{"type":415,"tag":783,"props":6185,"children":6186},{"style":1362},[6187],{"type":420,"value":6057},{"type":415,"tag":783,"props":6189,"children":6190},{"style":790},[6191],{"type":420,"value":755},{"type":415,"tag":783,"props":6193,"children":6194},{"style":1362},[6195],{"type":420,"value":5944},{"type":415,"tag":783,"props":6197,"children":6198},{"style":790},[6199],{"type":420,"value":755},{"type":415,"tag":783,"props":6201,"children":6202},{"style":3409},[6203],{"type":420,"value":6106},{"type":415,"tag":783,"props":6205,"children":6206},{"style":1362},[6207],{"type":420,"value":6208},"(audioBook)",{"type":415,"tag":783,"props":6210,"children":6211},{"style":790},[6212],{"type":420,"value":5744},{"type":415,"tag":783,"props":6214,"children":6215},{"class":785,"line":840},[6216,6220,6224],{"type":415,"tag":783,"props":6217,"children":6218},{"style":3409},[6219],{"type":420,"value":6077},{"type":415,"tag":783,"props":6221,"children":6222},{"style":1362},[6223],{"type":420,"value":5821},{"type":415,"tag":783,"props":6225,"children":6226},{"style":790},[6227],{"type":420,"value":5744},{"type":415,"tag":416,"props":6229,"children":6230},{},[6231,6233,6238,6239,6244,6246,6251,6253,6258],{"type":420,"value":6232},"To avoid having to use bind everywhere the ",{"type":415,"tag":550,"props":6234,"children":6236},{"className":6235},[],[6237],{"type":420,"value":5944},{"type":420,"value":4658},{"type":415,"tag":550,"props":6240,"children":6242},{"className":6241},[],[6243],{"type":420,"value":5951},{"type":420,"value":6245}," methods are used, we can do the ",{"type":415,"tag":550,"props":6247,"children":6249},{"className":6248},[],[6250],{"type":420,"value":6106},{"type":420,"value":6252}," thing directly in the constructor of the ",{"type":415,"tag":550,"props":6254,"children":6256},{"className":6255},[],[6257],{"type":420,"value":5929},{"type":420,"value":6259}," class.",{"type":415,"tag":773,"props":6261,"children":6263},{"className":5693,"code":6262,"language":5664,"meta":401,"style":401},"class AudioBook {\n    private isStarted = false;\n\n    constructor(public title: string) {\n        this.play = this.play.bind(this);\n        this.stop = this.stop.bind(this);\n    }\n    \n    play() {\n        this.isStarted = true;\n    }\n\n    stop() {\n        this.isStarted = false;\n    }\n}\n\nconst audioBook = new AudioBook(\"The Unicorn Project\");\nconst listenDevOpsBook = audioBook.play;\nlistenDevOpsBook();\n",[6264],{"type":415,"tag":550,"props":6265,"children":6266},{"__ignoreMap":401},[6267,6282,6305,6312,6347,6395,6442,6449,6456,6471,6494,6501,6508,6523,6546,6553,6560,6567,6614,6646],{"type":415,"tag":783,"props":6268,"children":6269},{"class":785,"line":786},[6270,6274,6278],{"type":415,"tag":783,"props":6271,"children":6272},{"style":805},[6273],{"type":420,"value":5706},{"type":415,"tag":783,"props":6275,"children":6276},{"style":875},[6277],{"type":420,"value":5711},{"type":415,"tag":783,"props":6279,"children":6280},{"style":790},[6281],{"type":420,"value":863},{"type":415,"tag":783,"props":6283,"children":6284},{"class":785,"line":796},[6285,6289,6293,6297,6301],{"type":415,"tag":783,"props":6286,"children":6287},{"style":805},[6288],{"type":420,"value":5723},{"type":415,"tag":783,"props":6290,"children":6291},{"style":929},[6292],{"type":420,"value":5728},{"type":415,"tag":783,"props":6294,"children":6295},{"style":790},[6296],{"type":420,"value":5733},{"type":415,"tag":783,"props":6298,"children":6299},{"style":5736},[6300],{"type":420,"value":5739},{"type":415,"tag":783,"props":6302,"children":6303},{"style":790},[6304],{"type":420,"value":5744},{"type":415,"tag":783,"props":6306,"children":6307},{"class":785,"line":840},[6308],{"type":415,"tag":783,"props":6309,"children":6310},{"emptyLinePlaceholder":5750},[6311],{"type":420,"value":5753},{"type":415,"tag":783,"props":6313,"children":6314},{"class":785,"line":866},[6315,6319,6323,6327,6331,6335,6339,6343],{"type":415,"tag":783,"props":6316,"children":6317},{"style":805},[6318],{"type":420,"value":5761},{"type":415,"tag":783,"props":6320,"children":6321},{"style":790},[6322],{"type":420,"value":5766},{"type":415,"tag":783,"props":6324,"children":6325},{"style":805},[6326],{"type":420,"value":5771},{"type":415,"tag":783,"props":6328,"children":6329},{"style":5388},[6330],{"type":420,"value":5776},{"type":415,"tag":783,"props":6332,"children":6333},{"style":790},[6334],{"type":420,"value":635},{"type":415,"tag":783,"props":6336,"children":6337},{"style":875},[6338],{"type":420,"value":5785},{"type":415,"tag":783,"props":6340,"children":6341},{"style":790},[6342],{"type":420,"value":2148},{"type":415,"tag":783,"props":6344,"children":6345},{"style":790},[6346],{"type":420,"value":863},{"type":415,"tag":783,"props":6348,"children":6349},{"class":785,"line":893},[6350,6354,6358,6362,6367,6371,6375,6379,6383,6387,6391],{"type":415,"tag":783,"props":6351,"children":6352},{"style":790},[6353],{"type":420,"value":5833},{"type":415,"tag":783,"props":6355,"children":6356},{"style":1362},[6357],{"type":420,"value":5944},{"type":415,"tag":783,"props":6359,"children":6360},{"style":790},[6361],{"type":420,"value":5733},{"type":415,"tag":783,"props":6363,"children":6364},{"style":790},[6365],{"type":420,"value":6366}," this.",{"type":415,"tag":783,"props":6368,"children":6369},{"style":1362},[6370],{"type":420,"value":5944},{"type":415,"tag":783,"props":6372,"children":6373},{"style":790},[6374],{"type":420,"value":755},{"type":415,"tag":783,"props":6376,"children":6377},{"style":3409},[6378],{"type":420,"value":6106},{"type":415,"tag":783,"props":6380,"children":6381},{"style":929},[6382],{"type":420,"value":5766},{"type":415,"tag":783,"props":6384,"children":6385},{"style":790},[6386],{"type":420,"value":5675},{"type":415,"tag":783,"props":6388,"children":6389},{"style":929},[6390],{"type":420,"value":2148},{"type":415,"tag":783,"props":6392,"children":6393},{"style":790},[6394],{"type":420,"value":5744},{"type":415,"tag":783,"props":6396,"children":6397},{"class":785,"line":920},[6398,6402,6406,6410,6414,6418,6422,6426,6430,6434,6438],{"type":415,"tag":783,"props":6399,"children":6400},{"style":790},[6401],{"type":420,"value":5833},{"type":415,"tag":783,"props":6403,"children":6404},{"style":1362},[6405],{"type":420,"value":5951},{"type":415,"tag":783,"props":6407,"children":6408},{"style":790},[6409],{"type":420,"value":5733},{"type":415,"tag":783,"props":6411,"children":6412},{"style":790},[6413],{"type":420,"value":6366},{"type":415,"tag":783,"props":6415,"children":6416},{"style":1362},[6417],{"type":420,"value":5951},{"type":415,"tag":783,"props":6419,"children":6420},{"style":790},[6421],{"type":420,"value":755},{"type":415,"tag":783,"props":6423,"children":6424},{"style":3409},[6425],{"type":420,"value":6106},{"type":415,"tag":783,"props":6427,"children":6428},{"style":929},[6429],{"type":420,"value":5766},{"type":415,"tag":783,"props":6431,"children":6432},{"style":790},[6433],{"type":420,"value":5675},{"type":415,"tag":783,"props":6435,"children":6436},{"style":929},[6437],{"type":420,"value":2148},{"type":415,"tag":783,"props":6439,"children":6440},{"style":790},[6441],{"type":420,"value":5744},{"type":415,"tag":783,"props":6443,"children":6444},{"class":785,"line":959},[6445],{"type":415,"tag":783,"props":6446,"children":6447},{"style":790},[6448],{"type":420,"value":1254},{"type":415,"tag":783,"props":6450,"children":6451},{"class":785,"line":997},[6452],{"type":415,"tag":783,"props":6453,"children":6454},{"style":1362},[6455],{"type":420,"value":5808},{"type":415,"tag":783,"props":6457,"children":6458},{"class":785,"line":1023},[6459,6463,6467],{"type":415,"tag":783,"props":6460,"children":6461},{"style":929},[6462],{"type":420,"value":5816},{"type":415,"tag":783,"props":6464,"children":6465},{"style":790},[6466],{"type":420,"value":5821},{"type":415,"tag":783,"props":6468,"children":6469},{"style":790},[6470],{"type":420,"value":863},{"type":415,"tag":783,"props":6472,"children":6473},{"class":785,"line":1061},[6474,6478,6482,6486,6490],{"type":415,"tag":783,"props":6475,"children":6476},{"style":790},[6477],{"type":420,"value":5833},{"type":415,"tag":783,"props":6479,"children":6480},{"style":1362},[6481],{"type":420,"value":5838},{"type":415,"tag":783,"props":6483,"children":6484},{"style":790},[6485],{"type":420,"value":5733},{"type":415,"tag":783,"props":6487,"children":6488},{"style":5736},[6489],{"type":420,"value":5847},{"type":415,"tag":783,"props":6491,"children":6492},{"style":790},[6493],{"type":420,"value":5744},{"type":415,"tag":783,"props":6495,"children":6496},{"class":785,"line":1099},[6497],{"type":415,"tag":783,"props":6498,"children":6499},{"style":790},[6500],{"type":420,"value":1254},{"type":415,"tag":783,"props":6502,"children":6503},{"class":785,"line":1137},[6504],{"type":415,"tag":783,"props":6505,"children":6506},{"emptyLinePlaceholder":5750},[6507],{"type":420,"value":5753},{"type":415,"tag":783,"props":6509,"children":6510},{"class":785,"line":1175},[6511,6515,6519],{"type":415,"tag":783,"props":6512,"children":6513},{"style":929},[6514],{"type":420,"value":5873},{"type":415,"tag":783,"props":6516,"children":6517},{"style":790},[6518],{"type":420,"value":5821},{"type":415,"tag":783,"props":6520,"children":6521},{"style":790},[6522],{"type":420,"value":863},{"type":415,"tag":783,"props":6524,"children":6525},{"class":785,"line":1213},[6526,6530,6534,6538,6542],{"type":415,"tag":783,"props":6527,"children":6528},{"style":790},[6529],{"type":420,"value":5833},{"type":415,"tag":783,"props":6531,"children":6532},{"style":1362},[6533],{"type":420,"value":5838},{"type":415,"tag":783,"props":6535,"children":6536},{"style":790},[6537],{"type":420,"value":5733},{"type":415,"tag":783,"props":6539,"children":6540},{"style":5736},[6541],{"type":420,"value":5739},{"type":415,"tag":783,"props":6543,"children":6544},{"style":790},[6545],{"type":420,"value":5744},{"type":415,"tag":783,"props":6547,"children":6548},{"class":785,"line":1239},[6549],{"type":415,"tag":783,"props":6550,"children":6551},{"style":790},[6552],{"type":420,"value":1254},{"type":415,"tag":783,"props":6554,"children":6555},{"class":785,"line":1248},[6556],{"type":415,"tag":783,"props":6557,"children":6558},{"style":790},[6559],{"type":420,"value":1272},{"type":415,"tag":783,"props":6561,"children":6562},{"class":785,"line":1257},[6563],{"type":415,"tag":783,"props":6564,"children":6565},{"emptyLinePlaceholder":5750},[6566],{"type":420,"value":5753},{"type":415,"tag":783,"props":6568,"children":6569},{"class":785,"line":1266},[6570,6574,6578,6582,6586,6590,6594,6598,6602,6606,6610],{"type":415,"tag":783,"props":6571,"children":6572},{"style":805},[6573],{"type":420,"value":5993},{"type":415,"tag":783,"props":6575,"children":6576},{"style":1362},[6577],{"type":420,"value":5998},{"type":415,"tag":783,"props":6579,"children":6580},{"style":790},[6581],{"type":420,"value":1993},{"type":415,"tag":783,"props":6583,"children":6584},{"style":790},[6585],{"type":420,"value":6007},{"type":415,"tag":783,"props":6587,"children":6588},{"style":3409},[6589],{"type":420,"value":5711},{"type":415,"tag":783,"props":6591,"children":6592},{"style":1362},[6593],{"type":420,"value":5766},{"type":415,"tag":783,"props":6595,"children":6596},{"style":790},[6597],{"type":420,"value":813},{"type":415,"tag":783,"props":6599,"children":6600},{"style":825},[6601],{"type":420,"value":6024},{"type":415,"tag":783,"props":6603,"children":6604},{"style":790},[6605],{"type":420,"value":813},{"type":415,"tag":783,"props":6607,"children":6608},{"style":1362},[6609],{"type":420,"value":2148},{"type":415,"tag":783,"props":6611,"children":6612},{"style":790},[6613],{"type":420,"value":5744},{"type":415,"tag":783,"props":6615,"children":6617},{"class":785,"line":6616},19,[6618,6622,6626,6630,6634,6638,6642],{"type":415,"tag":783,"props":6619,"children":6620},{"style":805},[6621],{"type":420,"value":5993},{"type":415,"tag":783,"props":6623,"children":6624},{"style":1362},[6625],{"type":420,"value":6048},{"type":415,"tag":783,"props":6627,"children":6628},{"style":790},[6629],{"type":420,"value":1993},{"type":415,"tag":783,"props":6631,"children":6632},{"style":1362},[6633],{"type":420,"value":6057},{"type":415,"tag":783,"props":6635,"children":6636},{"style":790},[6637],{"type":420,"value":755},{"type":415,"tag":783,"props":6639,"children":6640},{"style":1362},[6641],{"type":420,"value":5944},{"type":415,"tag":783,"props":6643,"children":6644},{"style":790},[6645],{"type":420,"value":5744},{"type":415,"tag":783,"props":6647,"children":6649},{"class":785,"line":6648},20,[6650,6654,6658],{"type":415,"tag":783,"props":6651,"children":6652},{"style":3409},[6653],{"type":420,"value":6077},{"type":415,"tag":783,"props":6655,"children":6656},{"style":1362},[6657],{"type":420,"value":5821},{"type":415,"tag":783,"props":6659,"children":6660},{"style":790},[6661],{"type":420,"value":5744},{"type":415,"tag":443,"props":6663,"children":6665},{"id":6664},"tool-of-the-week-durable-functions-monitor",[6666],{"type":420,"value":6667},"Tool of the week: Durable Functions Monitor ⚡",{"type":415,"tag":416,"props":6669,"children":6670},{},[6671,6673,6680],{"type":420,"value":6672},"If you are an Azure developer, you are probably already familiar with Azure Functions which is one of the solutions to do serverless in Azure. And, you may also have used Durable Functions to build serverless workflows. If I quote ",{"type":415,"tag":423,"props":6674,"children":6677},{"href":6675,"rel":6676},"https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview?tabs=csharp",[427],[6678],{"type":420,"value":6679},"Microsoft documentation",{"type":420,"value":6681},": \"Durable Functions is an extension of Azure Functions that lets you write stateful functions in a serverless compute environment.\" In concrete terms, if you are developing multiple Azure Functions and you want to orchestrate their execution while maintaining a state, Durable Functions are what you need. I like this technology a lot ❤️. When used correctly and for the right purpose, it can solve many issues you would face to implementing manually a workflow. Enough talk, let's go back to the tool of the week!",{"type":415,"tag":416,"props":6683,"children":6684},{},[6685],{"type":415,"tag":1322,"props":6686,"children":6690},{"alt":6687,"className":6688,"src":6689},"GitHub README of the Durable Functions Monitor project.",[1326,1327],"/posts/images/w202022tips_durablemonitor_1.png",[],{"type":415,"tag":416,"props":6692,"children":6693},{},[6694,6701],{"type":415,"tag":423,"props":6695,"children":6698},{"href":6696,"rel":6697},"https://github.com/microsoft/DurableFunctionsMonitor",[427],[6699],{"type":420,"value":6700},"Durable Functions Monitor",{"type":420,"value":6702}," is a UI tool that allows you to monitor, manage and debug your Azure Durable Functions. That's something very valuable because the tooling in Azure Portal is very poor for Durable Functions. Moreover, because a workflow can last for a long time and is often composed of many Azure Functions, it can be quite hard to understand at what stage the workflow is and what functions have already been executed 🤔. Durable Functions can help you with that and offers many other interesting features (functions graph, sequence diagram...). I heard about it a while ago but did not take the time to try it until recently and honestly it's too bad because it's a must-have to work with Azure Durable Functions 🚀!",{"type":415,"tag":588,"props":6704,"children":6705},{"icon":728},[6706],{"type":415,"tag":416,"props":6707,"children":6708},{},[6709],{"type":420,"value":6710},"It's worth noting that Durable Functions Monitor can be used as a vscode extension, as a Standalone service, or directly in your Function App.",{"type":415,"tag":443,"props":6712,"children":6714},{"id":6713},"git-tip-of-the-week-alias-to-force-push-commits",[6715],{"type":420,"value":6716},"Git tip of the week: alias to force push commits",{"type":415,"tag":416,"props":6718,"children":6719},{},[6720,6722,6729],{"type":420,"value":6721},"I often force push changes on my git branches. Indeed, I try to keep a clean and easy to read the history on my branches by using the ",{"type":415,"tag":423,"props":6723,"children":6726},{"href":6724,"rel":6725},"https://www.techwatching.dev/gitcheatsheet#when-you-want-to-have-a-clean-commit-history-on-your-branch-before-creating-your-pull-request",[427],[6727],{"type":420,"value":6728},"interactive rebase command",{"type":420,"value":6730}," so that it's easier for my colleagues to review my Pull Requests and I can use a rebase merging strategy instead of squashing my changes in a big commit when completing them.",{"type":415,"tag":416,"props":6732,"children":6733},{},[6734,6736,6742,6744,6750,6752,6758,6760,6766,6768,6773],{"type":420,"value":6735},"Before, I was using the ",{"type":415,"tag":550,"props":6737,"children":6739},{"className":6738},[],[6740],{"type":420,"value":6741},"git push --force",{"type":420,"value":6743}," command but reading a few articles on the topic convinced me that I should use the ",{"type":415,"tag":550,"props":6745,"children":6747},{"className":6746},[],[6748],{"type":420,"value":6749},"git push --force-with-lease",{"type":420,"value":6751}," command instead to avoid crushing commits colleagues could have done on my branch (even if there is little risk as I only rewrite the history of already pushed changes when working alone on a branch). You can read more about ",{"type":415,"tag":550,"props":6753,"children":6755},{"className":6754},[],[6756],{"type":420,"value":6757},"--force-with-lease",{"type":420,"value":6759}," on the git ",{"type":415,"tag":423,"props":6761,"children":6764},{"href":6762,"rel":6763},"https://git-scm.com/docs/git-push#Documentation/git-push.txt---force-with-leaseltrefnamegt",[427],[6765],{"type":420,"value":2391},{"type":420,"value":6767},". The only problem with this command is that it's a bit long to write so here is an alias to add to your ",{"type":415,"tag":550,"props":6769,"children":6771},{"className":6770},[],[6772],{"type":420,"value":3313},{"type":420,"value":635},{"type":415,"tag":773,"props":6775,"children":6779},{"className":6776,"code":6777,"language":6778,"meta":401,"style":401},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[alias]\n    pf = push origin --force-with-lease\n","yaml",[6780],{"type":415,"tag":550,"props":6781,"children":6782},{"__ignoreMap":401},[6783,6801],{"type":415,"tag":783,"props":6784,"children":6785},{"class":785,"line":786},[6786,6791,6796],{"type":415,"tag":783,"props":6787,"children":6788},{"style":790},[6789],{"type":420,"value":6790},"[",{"type":415,"tag":783,"props":6792,"children":6793},{"style":825},[6794],{"type":420,"value":6795},"alias",{"type":415,"tag":783,"props":6797,"children":6798},{"style":790},[6799],{"type":420,"value":6800},"]\n",{"type":415,"tag":783,"props":6802,"children":6803},{"class":785,"line":796},[6804],{"type":415,"tag":783,"props":6805,"children":6806},{"style":825},[6807],{"type":420,"value":6808},"    pf = push origin --force-with-lease\n",{"type":415,"tag":416,"props":6810,"children":6811},{},[6812],{"type":420,"value":2446},{"type":415,"tag":1420,"props":6814,"children":6815},{},[6816],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":6818},[6819,6821,6822],{"id":5655,"depth":796,"text":6820},"Reminder for my future self: don't forget about \"this\" in ts",{"id":6664,"depth":796,"text":6667},{"id":6713,"depth":796,"text":6716},"content:1.posts:38.w20-2022-tips-learned-this-week.md","1.posts/38.w20-2022-tips-learned-this-week.md",{"_path":118,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":117,"description":6826,"lead":6826,"date":6827,"image":6828,"badge":6829,"tags":6830,"body":6831,"_type":1435,"_id":7042,"_source":1437,"_file":7043,"_extension":1439},"Git commands in vscode, a nice tool for Vue developers and a must-have Visual Studio extension.","2022-05-14T00:00:00.000Z",{"src":2208},{"label":2210},[272,208,241,340,337,206],{"type":412,"children":6832,"toc":7037},[6833,6839,6871,6880,6885,6894,6906,6912,6959,6968,6981,6990,6996,7010,7019,7024,7033],{"type":415,"tag":443,"props":6834,"children":6836},{"id":6835},"git-tip-of-the-week",[6837],{"type":420,"value":6838},"Git tip of the week",{"type":415,"tag":416,"props":6840,"children":6841},{},[6842,6844,6851,6853,6860,6862,6869],{"type":420,"value":6843},"If you have read my ",{"type":415,"tag":423,"props":6845,"children":6848},{"href":6846,"rel":6847},"https://www.techwatching.dev/gitcheatsheet",[427],[6849],{"type":420,"value":6850},"git cheat sheet",{"type":420,"value":6852},", you know that I am a big fan of the ",{"type":415,"tag":423,"props":6854,"children":6857},{"href":6855,"rel":6856},"https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens",[427],[6858],{"type":420,"value":6859},"GitLens",{"type":420,"value":6861}," vscode extension. I have been using it for a while now but just discovered recently that there is a ",{"type":415,"tag":423,"props":6863,"children":6866},{"href":6864,"rel":6865},"https://github.com/gitkraken/vscode-gitlens#git-command-palette-",[427],[6867],{"type":420,"value":6868},"Git Command Palette",{"type":420,"value":6870}," that gives access to most common Git commands.",{"type":415,"tag":416,"props":6872,"children":6873},{},[6874],{"type":415,"tag":1322,"props":6875,"children":6879},{"alt":6876,"className":6877,"src":6878,"width":2413},"GitLens Command Palette in vscode",[1326,1327],"/posts/images/w192022tips_gitlens_1.png",[],{"type":415,"tag":416,"props":6881,"children":6882},{},[6883],{"type":420,"value":6884},"Usually, I prefer typing the git commands rather than using a visual tool. This way, I know exactly what I am doing (no magic commands done by a tool behind the scene), and I improve my knowledge of git. However, I think that with Git Command Palette, I get the best of both worlds. The UI helps me to use the git command I need without having to type everything and remember the exact syntax of the command. Yet, this is not a UI with buttons that hide from me the git commands being run. I am still aware of the exact git commands I am using and how.",{"type":415,"tag":416,"props":6886,"children":6887},{},[6888],{"type":415,"tag":1322,"props":6889,"children":6893},{"alt":6890,"className":6891,"src":6892,"width":2413},"Doing a Git fetch in GitLens Command Palette.",[1326,1327],"/posts/images/w192022tips_gitlens_2.png",[],{"type":415,"tag":416,"props":6895,"children":6896},{},[6897,6899,6905],{"type":420,"value":6898},"On Windows, the default shortcut to use the Git Command Palette is ",{"type":415,"tag":550,"props":6900,"children":6902},{"className":6901},[],[6903],{"type":420,"value":6904},"Ctrl + Shift + G : ",{"type":420,"value":755},{"type":415,"tag":443,"props":6907,"children":6909},{"id":6908},"tool-of-the-week-vue-telescope",[6910],{"type":420,"value":6911},"Tool of the week: Vue Telescope",{"type":415,"tag":416,"props":6913,"children":6914},{},[6915,6917,6924,6926,6933,6935,6941,6943,6950,6952,6957],{"type":420,"value":6916},"If you are a Vue developer and don't know this tool yet, this is going to make your day! When browsing a website, you are probably wondering if it has been made with Vue.js and if so what is the technology stack behind it. Personally, I find it very interesting to know which frameworks, libraries, or plugins have been used to create a website in Vue. And that's what ",{"type":415,"tag":423,"props":6918,"children":6921},{"href":6919,"rel":6920},"https://vuetelescope.com/",[427],[6922],{"type":420,"value":6923},"Vue Telescope",{"type":420,"value":6925}," is about. It's an open source tool made by ",{"type":415,"tag":423,"props":6927,"children":6930},{"href":6928,"rel":6929},"https://nuxtlabs.com/",[427],[6931],{"type":420,"value":6932},"NuxtLabs",{"type":420,"value":6934}," (the team behind the ",{"type":415,"tag":423,"props":6936,"children":6939},{"href":6937,"rel":6938},"https://nuxtjs.org/",[427],[6940],{"type":420,"value":1577},{"type":420,"value":6942}," framework) that detects the Vue technologies used in a website. It can be used from a browser ",{"type":415,"tag":423,"props":6944,"children":6947},{"href":6945,"rel":6946},"https://chrome.google.com/webstore/detail/vue-telescope/neaebjphlfplgdhedjdhcnpjkndddbpd",[427],[6948],{"type":420,"value":6949},"extension",{"type":420,"value":6951}," or from Vue Telescope's ",{"type":415,"tag":423,"props":6953,"children":6955},{"href":6919,"rel":6954},[427],[6956],{"type":420,"value":3231},{"type":420,"value":6958}," to search a analyze a specific website.",{"type":415,"tag":416,"props":6960,"children":6961},{},[6962],{"type":415,"tag":1322,"props":6963,"children":6967},{"alt":6964,"className":6965,"src":6966,"width":2413},"Vue Telescope extension showing Vue Telescope stack on its website.",[1326,1327],"/posts/images/w192022tips_vuetelescope_1.png",[],{"type":415,"tag":416,"props":6969,"children":6970},{},[6971,6973,6979],{"type":420,"value":6972},"You can explore the Vue.js websites already scanned by VueTelescope ",{"type":415,"tag":423,"props":6974,"children":6977},{"href":6975,"rel":6976},"https://vuetelescope.com/explore",[427],[6978],{"type":420,"value":2266},{"type":420,"value":6980}," and filter on the frameworks, UI Frameworks you are interested in.",{"type":415,"tag":416,"props":6982,"children":6983},{},[6984],{"type":415,"tag":1322,"props":6985,"children":6989},{"alt":6986,"className":6987,"src":6988,"width":2413},"Vue Telescope search.",[1326,1327],"/posts/images/w192022tips_vuetelescope_2.png",[],{"type":415,"tag":443,"props":6991,"children":6993},{"id":6992},"the-visual-studio-extension-you-should-try-add-new-file",[6994],{"type":420,"value":6995},"The Visual Studio extension you should try: Add New File",{"type":415,"tag":416,"props":6997,"children":6998},{},[6999,7001,7008],{"type":420,"value":7000},"Sometimes the simplest IDE extensions are the best. That's the case for the ",{"type":415,"tag":423,"props":7002,"children":7005},{"href":7003,"rel":7004},"https://marketplace.visualstudio.com/items?itemName=MadsKristensen.AddNewFile64",[427],[7006],{"type":420,"value":7007},"\"Add New\"",{"type":420,"value":7009}," Visual Studio extension which allows you to quickly create a new file by hitting \"Shift+F2\" and writing the name of the file with its extension. Nothing fancy, but it saves you a lot of time compared to adding a new file using the default dialog.",{"type":415,"tag":416,"props":7011,"children":7012},{},[7013],{"type":415,"tag":1322,"props":7014,"children":7018},{"alt":7015,"className":7016,"src":7017,"width":2413},"Website of Add New File Visual Studio extension.",[1326,1327],"/posts/images/w192022tips_addnewfile_1.png",[],{"type":415,"tag":416,"props":7020,"children":7021},{},[7022],{"type":420,"value":7023},"As you can see you can even create the missing folders where the file is placed.",{"type":415,"tag":416,"props":7025,"children":7026},{},[7027],{"type":415,"tag":1322,"props":7028,"children":7032},{"alt":7029,"className":7030,"src":7031,"width":2413},"Usage of Add New File in Visual Studio.",[1326,1327],"/posts/images/w192022tips_addnewfile.gif",[],{"type":415,"tag":416,"props":7034,"children":7035},{},[7036],{"type":420,"value":2446},{"title":401,"searchDepth":796,"depth":796,"links":7038},[7039,7040,7041],{"id":6835,"depth":796,"text":6838},{"id":6908,"depth":796,"text":6911},{"id":6992,"depth":796,"text":6995},"content:1.posts:37.w19-2022-tips-learned-this-week.md","1.posts/37.w19-2022-tips-learned-this-week.md",{"_path":109,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":108,"description":7045,"lead":7046,"date":7047,"image":7048,"badge":7049,"tags":7050,"body":7051,"_type":1435,"_id":7346,"_source":1437,"_file":7347,"_extension":1439},"This week we talk about code analysis in .NET, cron expressions with crontab guru, diagrams in Azure DevOps wikis, and sending HTTP requests in VS Code.","Tooling around .NET, Azure DevOps and VS Code.","2022-02-04T00:00:00.000Z",{"src":2208},{"label":2210},[272,239,349,343,352,208,206],{"type":412,"children":7052,"toc":7340},[7053,7057,7063,7110,7123,7132,7145,7154,7166,7175,7188,7194,7207,7216,7229,7235,7240,7283,7288,7297,7303,7314,7323,7336],{"type":415,"tag":416,"props":7054,"children":7055},{},[7056],{"type":420,"value":7045},{"type":415,"tag":443,"props":7058,"children":7060},{"id":7059},"net-tip-of-the-week-configuring-code-analysis-on-your-project",[7061],{"type":420,"value":7062},".NET tip of the week: configuring code analysis on your project",{"type":415,"tag":416,"props":7064,"children":7065},{},[7066,7068,7074,7076,7083,7085,7092,7094,7100,7102,7108],{"type":420,"value":7067},"Static code analysis is great because it helps you to have a better code quality, and it allows you to detect potential issues or bad practices in your code directly from your IDE. I knew about Roslyn Analyzers and the possibility to configure which rules are enabled or not (with their level of severity) through an ",{"type":415,"tag":550,"props":7069,"children":7071},{"className":7070},[],[7072],{"type":420,"value":7073},".editorconfig",{"type":420,"value":7075}," file. However, until I read this ",{"type":415,"tag":423,"props":7077,"children":7080},{"href":7078,"rel":7079},"https://endjin.com/blog/2022/01/raising-coding-standard-dotnet-analyzers.html",[427],[7081],{"type":420,"value":7082},"article",{"type":420,"value":7084}," a few days ago, I did not know that you could set a ",{"type":415,"tag":423,"props":7086,"children":7089},{"href":7087,"rel":7088},"https://docs.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#analysismode",[427],[7090],{"type":420,"value":7091},"predefined code analysis configuration",{"type":420,"value":7093}," just by setting an ",{"type":415,"tag":550,"props":7095,"children":7097},{"className":7096},[],[7098],{"type":420,"value":7099},"AnalysisMode",{"type":420,"value":7101}," property in your project file. This is great, especially if you don't want to lose time configuring all the rules individually in an ",{"type":415,"tag":550,"props":7103,"children":7105},{"className":7104},[],[7106],{"type":420,"value":7107},"editorconfig",{"type":420,"value":7109}," file. You can just choose the analysis mode you feel more appropriate and eventually disable a few rules that you don't want.",{"type":415,"tag":416,"props":7111,"children":7112},{},[7113,7115,7121],{"type":420,"value":7114},"For instance, in this ASP.NET Core 6 project I created from the built-in template, I added an empty interface in the ",{"type":415,"tag":550,"props":7116,"children":7118},{"className":7117},[],[7119],{"type":420,"value":7120},"Program.cs",{"type":420,"value":7122}," file.\nBy default I see a message indicating that I should declare my interface in a namespace.",{"type":415,"tag":416,"props":7124,"children":7125},{},[7126],{"type":415,"tag":1322,"props":7127,"children":7131},{"alt":7128,"className":7129,"src":7130},"Program in vscode with default analysis mode.",[1326,1327],"/posts/images/w052022tips_analysismode_1.png",[],{"type":415,"tag":416,"props":7133,"children":7134},{},[7135,7137,7143],{"type":420,"value":7136},"If I set the analysis mode to ",{"type":415,"tag":550,"props":7138,"children":7140},{"className":7139},[],[7141],{"type":420,"value":7142},"Recommended",{"type":420,"value":7144},", the code analysis indicates to me the same thing but this time as a warning.",{"type":415,"tag":416,"props":7146,"children":7147},{},[7148],{"type":415,"tag":1322,"props":7149,"children":7153},{"alt":7150,"className":7151,"src":7152},"Program in vscode with 'Recommended' analysis mode.",[1326,1327],"/posts/images/w052022tips_analysismode_2.png",[],{"type":415,"tag":416,"props":7155,"children":7156},{},[7157,7158,7164],{"type":420,"value":7136},{"type":415,"tag":550,"props":7159,"children":7161},{"className":7160},[],[7162],{"type":420,"value":7163},"All",{"type":420,"value":7165},", then not only do I have the previous warning but I also have a warning to tell me I should not have an empty interface.",{"type":415,"tag":416,"props":7167,"children":7168},{},[7169],{"type":415,"tag":1322,"props":7170,"children":7174},{"alt":7171,"className":7172,"src":7173},"Program in vscode with 'All' analysis mode.",[1326,1327],"/posts/images/w052022tips_analysismode_3.png",[],{"type":415,"tag":416,"props":7176,"children":7177},{},[7178,7180,7186],{"type":420,"value":7179},"There are also properties to only apply rules specific to a category (security for instance). You can check the ",{"type":415,"tag":423,"props":7181,"children":7184},{"href":7182,"rel":7183},"https://docs.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#analysismodecategory",[427],[7185],{"type":420,"value":2391},{"type":420,"value":7187}," to learn more about these.",{"type":415,"tag":443,"props":7189,"children":7191},{"id":7190},"tool-of-the-week-crontab-guru",[7192],{"type":420,"value":7193},"Tool of the week: crontab guru",{"type":415,"tag":416,"props":7195,"children":7196},{},[7197,7199,7205],{"type":420,"value":7198},"You are probably using cron expressions in a lot of different contexts whether it be to schedule an Azure Function or to trigger periodically a CI/CD pipeline (GitHub Actions or Azure DevOps for instance). Cron expressions are useful but you might not use them often, which makes it hard to remember the syntax. ",{"type":415,"tag":550,"props":7200,"children":7202},{"className":7201},[],[7203],{"type":420,"value":7204},"crontab guru",{"type":420,"value":7206}," is a tool that helps you create cron schedule expressions or understand what existing cron expressions mean.",{"type":415,"tag":416,"props":7208,"children":7209},{},[7210],{"type":415,"tag":1322,"props":7211,"children":7215},{"alt":7212,"className":7213,"src":7214},"Crontab Guru website.",[1326,1327],"/posts/images/w052022tips_crontab_guru.png",[],{"type":415,"tag":416,"props":7217,"children":7218},{},[7219,7221,7228],{"type":420,"value":7220},"It's really helpful, so add it to your ",{"type":415,"tag":423,"props":7222,"children":7225},{"href":7223,"rel":7224},"https://crontab.guru/",[427],[7226],{"type":420,"value":7227},"bookmarks",{"type":420,"value":755},{"type":415,"tag":443,"props":7230,"children":7232},{"id":7231},"the-azure-devops-tip-you-did-not-know-about-creating-diagrams-in-markdown-with-mermaidjs",[7233],{"type":420,"value":7234},"The Azure DevOps tip you did not know about: creating diagrams in markdown with Mermaid.js",{"type":415,"tag":416,"props":7236,"children":7237},{},[7238],{"type":420,"value":7239},"If you are using Azure DevOps, you are probably writing your technical documentation in markdown in a wiki. I like the idea of having \"documentation as code\" with markdown stored in a git repository that keeps the history of changes. Yet, sometimes documentation is not just about text, you want to have diagrams to properly illustrate what your text is explaining. And you don't want to have these diagrams just stored as images in your repository but you want them within the markdown to be modified as easily as the text.",{"type":415,"tag":416,"props":7241,"children":7242},{},[7243,7245,7251,7253,7259,7261,7266,7268,7273,7275,7282],{"type":420,"value":7244},"And guess what, that's possible thanks to ",{"type":415,"tag":550,"props":7246,"children":7248},{"className":7247},[],[7249],{"type":420,"value":7250},"Mermaid.js",{"type":420,"value":7252},". As you can read on its website, ",{"type":415,"tag":423,"props":7254,"children":7257},{"href":7255,"rel":7256},"https://mermaid-js.github.io/",[427],[7258],{"type":420,"value":7250},{"type":420,"value":7260}," is a \"JavaScript based diagramming and charting tool that renders Markdown-inspired text definitions to create and modify diagrams dynamically\". It means that you can write in your markdown file some text that describes a diagram and it will be rendered by ",{"type":415,"tag":550,"props":7262,"children":7264},{"className":7263},[],[7265],{"type":420,"value":7250},{"type":420,"value":7267},". There are a lot of types of diagrams that you can create using ",{"type":415,"tag":550,"props":7269,"children":7271},{"className":7270},[],[7272],{"type":420,"value":7250},{"type":420,"value":7274}," but Azure DevOps only ",{"type":415,"tag":423,"props":7276,"children":7279},{"href":7277,"rel":7278},"https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page",[427],[7280],{"type":420,"value":7281},"supports Sequence diagrams, Gantt Charts, and Flowcharts",{"type":420,"value":755},{"type":415,"tag":416,"props":7284,"children":7285},{},[7286],{"type":420,"value":7287},"Here is an example of a diagram I created in an Azure DevOps wiki:",{"type":415,"tag":416,"props":7289,"children":7290},{},[7291],{"type":415,"tag":1322,"props":7292,"children":7296},{"alt":7293,"className":7294,"src":7295},"Mermaid.js diagram in Azure DevOps wiki.",[1326,1327],"/posts/images/w052022tips_mermaid_1.png",[],{"type":415,"tag":443,"props":7298,"children":7300},{"id":7299},"the-vs-code-extension-you-should-try-rest-client",[7301],{"type":420,"value":7302},"The VS Code extension you should try: Rest Client",{"type":415,"tag":416,"props":7304,"children":7305},{},[7306,7312],{"type":415,"tag":423,"props":7307,"children":7310},{"href":7308,"rel":7309},"https://github.com/Huachao/vscode-restclient",[427],[7311],{"type":420,"value":2512},{"type":420,"value":7313}," is an open source VS Code extension that allows you to send HTTP requests and view the responses as you would do with Postman. But I prefer using REST Client over Postman because with REST Client you write the HTTP requests in text files (using the RFC 2616 standard) that you can version in your git repository along with your code. REST Client is not something I discovered this week, I have been using it for quite a long time but it's still very useful to me so I thought this post was a good opportunity to tell you to try it if you have not yet.",{"type":415,"tag":416,"props":7315,"children":7316},{},[7317],{"type":415,"tag":1322,"props":7318,"children":7322},{"alt":7319,"className":7320,"src":7321},"REST Client vscode extension.",[1326,1327],"/posts/images/w052022tips_restclient.png",[],{"type":415,"tag":416,"props":7324,"children":7325},{},[7326,7328,7334],{"type":420,"value":7327},"In fact, it's quite funny because the ",{"type":415,"tag":423,"props":7329,"children":7331},{"href":2485,"rel":7330},[427],[7332],{"type":420,"value":7333},"first article",{"type":420,"value":7335}," I wrote here on my blog was an article about REST Client, how to use it and why you should use it instead of Postman.",{"type":415,"tag":416,"props":7337,"children":7338},{},[7339],{"type":420,"value":2446},{"title":401,"searchDepth":796,"depth":796,"links":7341},[7342,7343,7344,7345],{"id":7059,"depth":796,"text":7062},{"id":7190,"depth":796,"text":7193},{"id":7231,"depth":796,"text":7234},{"id":7299,"depth":796,"text":7302},"content:1.posts:34.w05-2022-tips-learned-this-week.md","1.posts/34.w05-2022-tips-learned-this-week.md",{"_path":106,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":105,"description":7349,"lead":7350,"date":7351,"image":7352,"badge":7353,"tags":7354,"body":7355,"_type":1435,"_id":7780,"_source":1437,"_file":7781,"_extension":1439},".NET configuration providers, Vite vscode extension, Azure DevOps pull request templates, and degit.","Tooling around .NET, Azure DevOps, and VS Code.","2022-01-24T00:00:00.000Z",{"src":2208},{"label":2210},[272,239,263,343,208,346,241,206],{"type":412,"children":7356,"toc":7774},[7357,7363,7413,7422,7435,7530,7552,7558,7581,7586,7595,7601,7606,7620,7633,7646,7651,7660,7665,7670,7675,7684,7699,7705,7710,7745,7754,7766,7770],{"type":415,"tag":443,"props":7358,"children":7360},{"id":7359},"net-tip-of-the-week-the-new-way-to-add-a-configuration-source",[7361],{"type":420,"value":7362},".NET tip of the week: the new way to add a configuration source",{"type":415,"tag":416,"props":7364,"children":7365},{},[7366,7368,7374,7375,7381,7383,7389,7391,7396,7398,7403,7405,7412],{"type":420,"value":7367},".NET 6 introduced a new way to build a .NET application using the new ",{"type":415,"tag":550,"props":7369,"children":7371},{"className":7370},[],[7372],{"type":420,"value":7373},"WebApplication",{"type":420,"value":3487},{"type":415,"tag":550,"props":7376,"children":7378},{"className":7377},[],[7379],{"type":420,"value":7380},"WebApplicationBuilder",{"type":420,"value":7382}," classes. One thing I like about it is how configuration is handled. Instead of using the ",{"type":415,"tag":550,"props":7384,"children":7386},{"className":7385},[],[7387],{"type":420,"value":7388},"ConfigureAppConfiguration",{"type":420,"value":7390}," method to add a new configuration source, you can directly use the ",{"type":415,"tag":550,"props":7392,"children":7394},{"className":7393},[],[7395],{"type":420,"value":263},{"type":420,"value":7397}," property on the ",{"type":415,"tag":550,"props":7399,"children":7401},{"className":7400},[],[7402],{"type":420,"value":7380},{"type":420,"value":7404}," instance. You can see an example of this change on this screenshot of the ",{"type":415,"tag":423,"props":7406,"children":7409},{"href":7407,"rel":7408},"https://docs.microsoft.com/en-us/aspnet/core/migration/50-to-60-samples?view=aspnetcore-6.0#add-configuration-providers",[427],[7410],{"type":420,"value":7411},"ASP.NET Core documentation",{"type":420,"value":635},{"type":415,"tag":416,"props":7414,"children":7415},{},[7416],{"type":415,"tag":1322,"props":7417,"children":7421},{"alt":7418,"className":7419,"src":7420},"Using configuration provider in ASP.NET 5 versus ASP.NET 6.",[1326,1327],"/posts/images/w032022tips_net_configuration_1.png",[],{"type":415,"tag":416,"props":7423,"children":7424},{},[7425,7427,7433],{"type":420,"value":7426},"You may think this way of adding a new configuration source does not bring much apart from making the code shorter. I thought it too until I discovered that it enables you to access configuration in the builder from previously registered configuration sources. For instance, if you want to load secrets from an Azure Key Vault into your configuration, you will need to retrieve the Key Vault URI from your configuration (that you may have set in your ",{"type":415,"tag":550,"props":7428,"children":7430},{"className":7429},[],[7431],{"type":420,"value":7432},"appsettings.Development.json",{"type":420,"value":7434}," file). Before you would have to partially build your configuration to get the value of a setting, now you can just access it.",{"type":415,"tag":773,"props":7436,"children":7439},{"className":7437,"code":7438,"language":326,"meta":401,"style":401},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration[\"KeyVault:VaultUri\"]), new DefaultAzureCredential());\n",[7440],{"type":415,"tag":550,"props":7441,"children":7442},{"__ignoreMap":401},[7443],{"type":415,"tag":783,"props":7444,"children":7445},{"class":785,"line":786},[7446,7451,7455,7459,7463,7468,7473,7478,7482,7486,7490,7494,7498,7502,7507,7511,7516,7520,7525],{"type":415,"tag":783,"props":7447,"children":7448},{"style":1362},[7449],{"type":420,"value":7450},"builder",{"type":415,"tag":783,"props":7452,"children":7453},{"style":790},[7454],{"type":420,"value":755},{"type":415,"tag":783,"props":7456,"children":7457},{"style":1362},[7458],{"type":420,"value":263},{"type":415,"tag":783,"props":7460,"children":7461},{"style":790},[7462],{"type":420,"value":755},{"type":415,"tag":783,"props":7464,"children":7465},{"style":3409},[7466],{"type":420,"value":7467},"AddAzureKeyVault",{"type":415,"tag":783,"props":7469,"children":7470},{"style":790},[7471],{"type":420,"value":7472},"(new",{"type":415,"tag":783,"props":7474,"children":7475},{"style":875},[7476],{"type":420,"value":7477}," Uri",{"type":415,"tag":783,"props":7479,"children":7480},{"style":790},[7481],{"type":420,"value":5766},{"type":415,"tag":783,"props":7483,"children":7484},{"style":1362},[7485],{"type":420,"value":7450},{"type":415,"tag":783,"props":7487,"children":7488},{"style":790},[7489],{"type":420,"value":755},{"type":415,"tag":783,"props":7491,"children":7492},{"style":1362},[7493],{"type":420,"value":263},{"type":415,"tag":783,"props":7495,"children":7496},{"style":790},[7497],{"type":420,"value":6790},{"type":415,"tag":783,"props":7499,"children":7500},{"style":790},[7501],{"type":420,"value":813},{"type":415,"tag":783,"props":7503,"children":7504},{"style":825},[7505],{"type":420,"value":7506},"KeyVault:VaultUri",{"type":415,"tag":783,"props":7508,"children":7509},{"style":790},[7510],{"type":420,"value":813},{"type":415,"tag":783,"props":7512,"children":7513},{"style":790},[7514],{"type":420,"value":7515},"]),",{"type":415,"tag":783,"props":7517,"children":7518},{"style":790},[7519],{"type":420,"value":6007},{"type":415,"tag":783,"props":7521,"children":7522},{"style":875},[7523],{"type":420,"value":7524}," DefaultAzureCredential",{"type":415,"tag":783,"props":7526,"children":7527},{"style":790},[7528],{"type":420,"value":7529},"());\n",{"type":415,"tag":416,"props":7531,"children":7532},{},[7533,7535,7542,7544,7550],{"type":420,"value":7534},"Configuration from previously registered sources is already available because configuration sources are directly loaded once they are added. If you want to learn more about the behind-the-scenes, Andrew Lock has a ",{"type":415,"tag":423,"props":7536,"children":7539},{"href":7537,"rel":7538},"https://andrewlock.net/exploring-dotnet-6-part-1-looking-inside-configurationmanager-in-dotnet-6/",[427],[7540],{"type":420,"value":7541},"very interesting article",{"type":420,"value":7543}," about ",{"type":415,"tag":550,"props":7545,"children":7547},{"className":7546},[],[7548],{"type":420,"value":7549},"ConfigurationManager",{"type":420,"value":7551}," that I suggest you read.",{"type":415,"tag":443,"props":7553,"children":7555},{"id":7554},"the-vs-code-extension-you-should-try-vite",[7556],{"type":420,"value":7557},"The VS Code extension you should try: Vite",{"type":415,"tag":416,"props":7559,"children":7560},{},[7561,7563,7570,7572,7579],{"type":420,"value":7562},"If you are developing a front-end using Vite (and there are ",{"type":415,"tag":423,"props":7564,"children":7567},{"href":7565,"rel":7566},"https://vitejs.dev/guide/why.html",[427],[7568],{"type":420,"value":7569},"good reasons",{"type":420,"value":7571}," why you should), there is a ",{"type":415,"tag":423,"props":7573,"children":7576},{"href":7574,"rel":7575},"https://marketplace.visualstudio.com/items?itemName=antfu.vite",[427],[7577],{"type":420,"value":7578},"Vite extension for VS Code",{"type":420,"value":7580}," currently in preview.",{"type":415,"tag":416,"props":7582,"children":7583},{},[7584],{"type":420,"value":7585},"You can see the main features of this extension below: little things that make you always more productive.",{"type":415,"tag":416,"props":7587,"children":7588},{},[7589],{"type":415,"tag":1322,"props":7590,"children":7594},{"alt":7591,"className":7592,"src":7593},"Vs code vite exension.",[1326,1327],"/posts/images/w032022tips_vscode_vite_1.png",[],{"type":415,"tag":443,"props":7596,"children":7598},{"id":7597},"the-azure-devops-tip-you-did-not-know-about-creating-pull-requests-templates",[7599],{"type":420,"value":7600},"The Azure DevOps tip you did not know about: creating pull requests templates",{"type":415,"tag":416,"props":7602,"children":7603},{},[7604],{"type":420,"value":7605},"As a developer working with Azure DevOps, you probably spend a lot of time reviewing your colleagues pull requests and what helps you to have a good context (of what problem a pull request solves or what new feature it brings) are having a work item associated to the pull request, and having a good description. You can enforce the pull request to have an associated work item by setting it to mandatory in your branch policies, but \"having a good description\" is not something you can enforce.",{"type":415,"tag":416,"props":7607,"children":7608},{},[7609,7611,7618],{"type":420,"value":7610},"However, you can provide some guidance on what the description should tell, how it should be organized, what are the key points to verify before submitting the pull request... How do you do that? By creating a pull request template in your repository which will be a markdown file that will be automatically added to a pull request description when the pull request is created. You can read the ",{"type":415,"tag":423,"props":7612,"children":7615},{"href":7613,"rel":7614},"https://docs.microsoft.com/en-us/azure/devops/repos/git/pull-request-templates?view=azure-devops",[427],[7616],{"type":420,"value":7617},"official documentation",{"type":420,"value":7619}," but let me show you quickly how it works:",{"type":415,"tag":416,"props":7621,"children":7622},{},[7623,7625,7631],{"type":420,"value":7624},"1- You create a ",{"type":415,"tag":550,"props":7626,"children":7628},{"className":7627},[],[7629],{"type":420,"value":7630},".azuredevops",{"type":420,"value":7632}," folder in the root of your git repository",{"type":415,"tag":416,"props":7634,"children":7635},{},[7636,7638,7644],{"type":420,"value":7637},"2- You create a markdown file ",{"type":415,"tag":550,"props":7639,"children":7641},{"className":7640},[],[7642],{"type":420,"value":7643},"pull_request_template.md",{"type":420,"value":7645}," containing the description you want",{"type":415,"tag":416,"props":7647,"children":7648},{},[7649],{"type":420,"value":7650},"You can see below an example of a template I created:",{"type":415,"tag":416,"props":7652,"children":7653},{},[7654],{"type":415,"tag":1322,"props":7655,"children":7659},{"alt":7656,"className":7657,"src":7658},"Pull request markdown template file.",[1326,1327],"/posts/images/w032022tips_pr_template_1.png",[],{"type":415,"tag":416,"props":7661,"children":7662},{},[7663],{"type":420,"value":7664},"3- You commit this file and push it in your main branch",{"type":415,"tag":416,"props":7666,"children":7667},{},[7668],{"type":420,"value":7669},"4- Now when someone creates a pull request, he will have a pre-filled description to complete before submitting his pull request",{"type":415,"tag":416,"props":7671,"children":7672},{},[7673],{"type":420,"value":7674},"This is what it looks like for my template:",{"type":415,"tag":416,"props":7676,"children":7677},{},[7678],{"type":415,"tag":1322,"props":7679,"children":7683},{"alt":7680,"className":7681,"src":7682},"Pull request template in Azure DevOps.",[1326,1327],"/posts/images/w032022tips_pr_template_2.png",[],{"type":415,"tag":588,"props":7685,"children":7686},{"icon":590},[7687],{"type":415,"tag":416,"props":7688,"children":7689},{},[7690,7692,7698],{"type":420,"value":7691},"If you are using GitHub and not Azure DevOps, just know that there are also pull requests templates in ",{"type":415,"tag":423,"props":7693,"children":7696},{"href":7694,"rel":7695},"https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository",[427],[7697],{"type":420,"value":233},{"type":420,"value":755},{"type":415,"tag":443,"props":7700,"children":7702},{"id":7701},"tool-of-the-week-degit",[7703],{"type":420,"value":7704},"Tool of the week: degit",{"type":415,"tag":416,"props":7706,"children":7707},{},[7708],{"type":420,"value":7709},"Sometimes you find an interesting open source git repository that could help you save some time when starting a new project. That can be a project template or a sample, it's a repository that you want to start coding from. You can clone it or fork it but you will retrieve the whole git history and that is something that you probably don't want.",{"type":415,"tag":416,"props":7711,"children":7712},{},[7713,7720,7722,7728,7730,7736,7738,7744],{"type":415,"tag":423,"props":7714,"children":7717},{"href":7715,"rel":7716},"https://github.com/Rich-Harris/degit",[427],[7718],{"type":420,"value":7719},"degit",{"type":420,"value":7721}," is a helpful tool that solves this issue. When you run this tool on a git repository, it retrieves locally the latest version of this repository without its git history. It's an ",{"type":415,"tag":550,"props":7723,"children":7725},{"className":7724},[],[7726],{"type":420,"value":7727},"npm",{"type":420,"value":7729}," tool so you can install it globally with ",{"type":415,"tag":550,"props":7731,"children":7733},{"className":7732},[],[7734],{"type":420,"value":7735},"npm install -g degit",{"type":420,"value":7737}," or directly run it using ",{"type":415,"tag":550,"props":7739,"children":7741},{"className":7740},[],[7742],{"type":420,"value":7743},"npx",{"type":420,"value":755},{"type":415,"tag":416,"props":7746,"children":7747},{},[7748],{"type":415,"tag":1322,"props":7749,"children":7753},{"alt":7750,"className":7751,"src":7752},"Ouput when using degit package on a repository.",[1326,1327],"/posts/images/w032022tips_degit_1.png",[],{"type":415,"tag":416,"props":7755,"children":7756},{},[7757,7759,7764],{"type":420,"value":7758},"Don't hesitate to give the project a star on ",{"type":415,"tag":423,"props":7760,"children":7762},{"href":7715,"rel":7761},[427],[7763],{"type":420,"value":233},{"type":420,"value":7765}," if you find it useful.",{"type":415,"tag":416,"props":7767,"children":7768},{},[7769],{"type":420,"value":2446},{"type":415,"tag":1420,"props":7771,"children":7772},{},[7773],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":7775},[7776,7777,7778,7779],{"id":7359,"depth":796,"text":7362},{"id":7554,"depth":796,"text":7557},{"id":7597,"depth":796,"text":7600},{"id":7701,"depth":796,"text":7704},"content:1.posts:33.w03-2022-tips-learned-this-week.md","1.posts/33.w03-2022-tips-learned-this-week.md",{"_path":103,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":102,"description":7783,"lead":7784,"date":7785,"image":7786,"badge":7787,"tags":7788,"body":7789,"_type":1435,"_id":8022,"_source":1437,"_file":8023,"_extension":1439},"This is my first article of the series Tips I learned this week for 2022 🚀! And today we are going to see some tips about .NET, Azure, GitHub, and VS Code.","Tooling around .NET, Azure, Github and VS Code.","2022-01-14T00:00:00.000Z",{"src":2208},{"label":2210},[272,239,225,233,208,206],{"type":412,"children":7790,"toc":8015},[7791,7805,7811,7824,7833,7838,7850,7862,7892,7901,7907,7921,7934,7947,7956,7969,7975,7989,7998,8011],{"type":415,"tag":416,"props":7792,"children":7793},{},[7794,7796,7803],{"type":420,"value":7795},"This is my first article of the series ",{"type":415,"tag":423,"props":7797,"children":7800},{"href":7798,"rel":7799},"https://www.techwatching.dev/tags/tips-learned-this-week/",[427],[7801],{"type":420,"value":7802},"Tips I learned this week",{"type":420,"value":7804}," for 2022 🚀! And today we are going to see some tips about .NET, Azure, GitHub, and VS Code.",{"type":415,"tag":443,"props":7806,"children":7808},{"id":7807},"net-tip-of-the-week-changing-the-net-cli-language",[7809],{"type":420,"value":7810},".NET tip of the week: changing the .NET CLI language",{"type":415,"tag":416,"props":7812,"children":7813},{},[7814,7816,7822],{"type":420,"value":7815},"Did you know that you could change the language of the .NET CLI to the one you find most appropriate? By default, the dotnet CLI messages you see when running a dotnet program are your language OS (French in my case). However, by setting the \"DOTNET_CLI_UI_LANGUAGE\" variable environment to ",{"type":415,"tag":550,"props":7817,"children":7819},{"className":7818},[],[7820],{"type":420,"value":7821},"en",{"type":420,"value":7823}," for instance I can change it to English.",{"type":415,"tag":416,"props":7825,"children":7826},{},[7827],{"type":415,"tag":1322,"props":7828,"children":7832},{"alt":7829,"className":7830,"src":7831},".NET CLI language change in terminal.",[1326,1327],"/posts/images/w022022tips_dotnet_cli.png",[],{"type":415,"tag":416,"props":7834,"children":7835},{},[7836],{"type":420,"value":7837},"Why is it useful? Let's imagine I have an issue with a dotnet CLI command and that I want to get some help from the community by posting a question to a Q&A website with a screenshot of my error. If all the messages are in French I will probably not get many answers whereas if it's in English everyone will be able to understand what my commands are doing. There is also the fact that sometimes French accents and special characters are not well displayed by some terminals.",{"type":415,"tag":443,"props":7839,"children":7841},{"id":7840},"the-azure-tip-you-did-not-know-about-simplify-your-azure-cli-configuration-with-azure-init",[7842,7844],{"type":420,"value":7843},"The Azure tip you did not know about: simplify your Azure CLI configuration with ",{"type":415,"tag":550,"props":7845,"children":7847},{"className":7846},[],[7848],{"type":420,"value":7849},"azure init",{"type":415,"tag":416,"props":7851,"children":7852},{},[7853,7855,7860],{"type":420,"value":7854},"I don't know if you use a lot Azure CLI but it's a very nice tool! Each time I use Azure CLI, I think \"it's awesome 🤩 I should use it more often instead of using Azure Portal\". I even wrote a post about that 2 years ago: \"",{"type":415,"tag":423,"props":7856,"children":7858},{"href":4880,"rel":7857},[427],[7859],{"type":420,"value":15},{"type":420,"value":7861},"\".",{"type":415,"tag":416,"props":7863,"children":7864},{},[7865,7867,7873,7875,7882,7884,7890],{"type":420,"value":7866},"However, if you want to configure your Azure CLI, it can be sometimes a bit boring and slow to configure it using the ",{"type":415,"tag":550,"props":7868,"children":7870},{"className":7869},[],[7871],{"type":420,"value":7872},"az config",{"type":420,"value":7874}," command. That is why Microsoft has ",{"type":415,"tag":423,"props":7876,"children":7879},{"href":7877,"rel":7878},"https://techcommunity.microsoft.com/t5/azure-tools-blog/streamline-configuring-azure-cli-with-az-init/ba-p/3051810",[427],[7880],{"type":420,"value":7881},"released in preview",{"type":420,"value":7883}," an Azure CLI extension called ",{"type":415,"tag":550,"props":7885,"children":7887},{"className":7886},[],[7888],{"type":420,"value":7889},"az init",{"type":420,"value":7891}," to simplify this configuration. For example you can quickly configure the output of the commands or the syntax highlighting, things like that.",{"type":415,"tag":416,"props":7893,"children":7894},{},[7895],{"type":415,"tag":1322,"props":7896,"children":7900},{"alt":7897,"className":7898,"src":7899},"azure init command output.",[1326,1327],"/posts/images/w022022tips_az_init.png",[],{"type":415,"tag":443,"props":7902,"children":7904},{"id":7903},"tool-of-the-week-github-code-search",[7905],{"type":420,"value":7906},"Tool of the week: GitHub Code Search",{"type":415,"tag":416,"props":7908,"children":7909},{},[7910,7912,7919],{"type":420,"value":7911},"Have you ever wished you could easily search code on GitHub in multiple repositories without cloning anything? That is now possible with ",{"type":415,"tag":423,"props":7913,"children":7916},{"href":7914,"rel":7915},"https://cs.github.com/",[427],[7917],{"type":420,"value":7918},"GitHub Code Search",{"type":420,"value":7920},". It is still in preview but looks promising! You have access to nice filters to find exactly the code you are looking for, and once you get it you can navigate in and across files.",{"type":415,"tag":416,"props":7922,"children":7923},{},[7924,7926,7932],{"type":420,"value":7925},"For a long time, I have kept a bookmark to the \"",{"type":415,"tag":423,"props":7927,"children":7930},{"href":7928,"rel":7929},"https://source.dot.net/",[427],[7931],{"type":420,"value":7928},{"type":420,"value":7933},"\" website for the times when I needed to understand how something was implemented in the .NET Core framework. But with GitHub Code Search I think I don't need it anymore. .NET Core is open source and all the source code is on GitHub so I can quickly find everything I need just by searching it on GitHub.",{"type":415,"tag":416,"props":7935,"children":7936},{},[7937,7939,7945],{"type":420,"value":7938},"For instance, let's say I don't remember exactly what configuration is injected by default in a dotnet project when you use the ",{"type":415,"tag":550,"props":7940,"children":7942},{"className":7941},[],[7943],{"type":420,"value":7944},"Host.CreateDefaultBuilder",{"type":420,"value":7946}," method. I will scope my search to dotnet organization repositories and a few keystrokes later, I can see all the configuration providers used to load some default configuration in a project.",{"type":415,"tag":416,"props":7948,"children":7949},{},[7950],{"type":415,"tag":1322,"props":7951,"children":7955},{"alt":7952,"className":7953,"src":7954},"GitHub search usage.",[1326,1327],"/posts/images/w022022tips_githu_cs.gif",[],{"type":415,"tag":416,"props":7957,"children":7958},{},[7959,7961,7968],{"type":420,"value":7960},"You can read more about GitHub Code Search on ",{"type":415,"tag":423,"props":7962,"children":7965},{"href":7963,"rel":7964},"https://github.blog/2021-12-08-improving-github-code-search/",[427],[7966],{"type":420,"value":7967},"GitHub's blog",{"type":420,"value":755},{"type":415,"tag":443,"props":7970,"children":7972},{"id":7971},"the-vs-code-extension-you-should-try-i18n-ally",[7973],{"type":420,"value":7974},"The VS Code extension you should try: i18n ally",{"type":415,"tag":416,"props":7976,"children":7977},{},[7978,7980,7987],{"type":420,"value":7979},"When you are developing an application that supports several languages, it can quickly become annoying to go in every translation file when you need to check or modify a translation. If you are developing your application using VS Code, you probably should check out the extension ",{"type":415,"tag":423,"props":7981,"children":7984},{"href":7982,"rel":7983},"https://github.com/lokalise/i18n-ally",[427],[7985],{"type":420,"value":7986},"i18n ally",{"type":420,"value":7988}," because it will save you a lot of time.",{"type":415,"tag":416,"props":7990,"children":7991},{},[7992],{"type":415,"tag":1322,"props":7993,"children":7997},{"alt":7994,"className":7995,"src":7996},"i18n ally extension in vscode.",[1326,1327],"/posts/images/w222021tips_i18n_ally.png",[],{"type":415,"tag":416,"props":7999,"children":8000},{},[8001,8003,8009],{"type":420,"value":8002},"It is very handy and I like the fact the extension support lots of frameworks like Vue.js, Angular, React, Svelte, and Flutter (you can find the complete list of supported frameworks ",{"type":415,"tag":423,"props":8004,"children":8007},{"href":8005,"rel":8006},"https://github.com/lokalise/i18n-ally/wiki/Supported-Frameworks",[427],[8008],{"type":420,"value":2266},{"type":420,"value":8010},"). My only regret with this extension is not to have heard about it sooner.",{"type":415,"tag":416,"props":8012,"children":8013},{},[8014],{"type":420,"value":2446},{"title":401,"searchDepth":796,"depth":796,"links":8016},[8017,8018,8020,8021],{"id":7807,"depth":796,"text":7810},{"id":7840,"depth":796,"text":8019},"The Azure tip you did not know about: simplify your Azure CLI configuration with azure init",{"id":7903,"depth":796,"text":7906},{"id":7971,"depth":796,"text":7974},"content:1.posts:32.w02-2022-tips-learned-this-week.md","1.posts/32.w02-2022-tips-learned-this-week.md",{"_path":94,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":93,"description":8025,"date":8026,"lead":8027,"image":8028,"badge":8030,"tags":8031,"body":8032,"_type":1435,"_id":8337,"_source":1437,"_file":8338,"_extension":1439},"Do you often star a GitHub repository and later when you want to get back to it struggle to find it on your stars page? I do! Well, I did... until I discovered Astral, the web application that allows you to Organize Your GitHub Stars With Ease. That is what we are going to talk about in this article.","2021-11-28T00:00:00.000Z","Another way to bookmark GitHub repositories.",{"src":8029},"/images/stars_1.jpg",{"label":408},[206,233],{"type":412,"children":8033,"toc":8330},[8034,8056,8062,8084,8089,8098,8103,8112,8118,8123,8146,8151,8157,8168,8177,8182,8187,8210,8219,8227,8233,8238,8243,8261,8269,8278,8283,8304,8313,8317],{"type":415,"tag":416,"props":8035,"children":8036},{},[8037,8039,8046,8048,8054],{"type":420,"value":8038},"Do you often star a GitHub repository and later when you want to get back to it struggle to find it on your stars page? I do! Well, I did... until I discovered ",{"type":415,"tag":423,"props":8040,"children":8043},{"href":8041,"rel":8042},"https://astralapp.com/",[427],[8044],{"type":420,"value":8045},"Astral",{"type":420,"value":8047},", the web application that allows you to ",{"type":415,"tag":550,"props":8049,"children":8051},{"className":8050},[],[8052],{"type":420,"value":8053},"Organize Your GitHub Stars With Ease",{"type":420,"value":8055},". That is what we are going to talk about in this article.",{"type":415,"tag":443,"props":8057,"children":8059},{"id":8058},"github-stars-as-a-bookmarking-tool",[8060],{"type":420,"value":8061},"GitHub stars as a bookmarking tool.",{"type":415,"tag":416,"props":8063,"children":8064},{},[8065,8067,8074,8076,8082],{"type":420,"value":8066},"When you find an interesting GitHub repository, instead of adding it to your browser bookmarks among a lot of other websites, you can star it. As ",{"type":415,"tag":423,"props":8068,"children":8071},{"href":8069,"rel":8070},"https://docs.github.com/en/get-started/exploring-projects-on-github/saving-repositories-with-stars",[427],[8072],{"type":420,"value":8073},"GitHub Docs",{"type":420,"value":8075}," say: ",{"type":415,"tag":550,"props":8077,"children":8079},{"className":8078},[],[8080],{"type":420,"value":8081},"Starring make it easy to find a repository again later",{"type":420,"value":8083},". Indeed usually, when I come across a nice GitHub repository, I want to go back to it later (to deep dive into the awesome framework I discover, to try this new open source tool, to have this useful sample somewhere when I will need it...).",{"type":415,"tag":416,"props":8085,"children":8086},{},[8087],{"type":420,"value":8088},"As you can see below, all the GitHub repositories you have starred can be found on your GitHub stars page.",{"type":415,"tag":416,"props":8090,"children":8091},{},[8092],{"type":415,"tag":1322,"props":8093,"children":8097},{"alt":8094,"className":8095,"src":8096},"\"Your stars\" menu in GitHub.",[1326,1327],"/posts/images/astral_github_1.png",[],{"type":415,"tag":416,"props":8099,"children":8100},{},[8101],{"type":420,"value":8102},"In addition to that, you can sort, filter (by language or topic), and search starred repositories:",{"type":415,"tag":416,"props":8104,"children":8105},{},[8106],{"type":415,"tag":1322,"props":8107,"children":8111},{"alt":8108,"className":8109,"src":8110},"Starred GitHub repositories filters.",[1326,1327],"/posts/images/astral_github_2.png",[],{"type":415,"tag":443,"props":8113,"children":8115},{"id":8114},"have-you-seen-that-repository-i-am-looking-for",[8116],{"type":420,"value":8117},"Have you seen that repository I am looking for?",{"type":415,"tag":416,"props":8119,"children":8120},{},[8121],{"type":420,"value":8122},"Using GitHub stars to bookmark the repositories you are interested in is great but if you do it a lot, it can become hard to find later the repository you want. Even if it is possible to sort, filter, and search repositories, it is sometimes impossible to quickly find what you are looking for. And there are good reasons for that:",{"type":415,"tag":493,"props":8124,"children":8125},{},[8126,8131,8136,8141],{"type":415,"tag":497,"props":8127,"children":8128},{},[8129],{"type":420,"value":8130},"You have starred this repository a long time ago, and it does not stand out from other repositories in terms of activity or number of stars so sorting won't help you",{"type":415,"tag":497,"props":8132,"children":8133},{},[8134],{"type":420,"value":8135},"Most of your starred repositories are in the same language so filters are useless",{"type":415,"tag":497,"props":8137,"children":8138},{},[8139],{"type":420,"value":8140},"The repository was not tagged with a topic that could help you to find it",{"type":415,"tag":497,"props":8142,"children":8143},{},[8144],{"type":420,"value":8145},"You don't remember the name of the repository",{"type":415,"tag":416,"props":8147,"children":8148},{},[8149],{"type":420,"value":8150},"The problem is that starred repositories can only be filtered with information that is not particularly useful to you. What we would need is something to organize the starred repositories in a way relevant to us. And here comes Astral.",{"type":415,"tag":443,"props":8152,"children":8154},{"id":8153},"what-is-astral",[8155],{"type":420,"value":8156},"What is Astral?",{"type":415,"tag":416,"props":8158,"children":8159},{},[8160,8166],{"type":415,"tag":423,"props":8161,"children":8164},{"href":8162,"rel":8163},"https://github.com/astralapp/astral",[427],[8165],{"type":420,"value":8045},{"type":420,"value":8167}," is an open source web application that helps you organize your starred repositories.",{"type":415,"tag":416,"props":8169,"children":8170},{},[8171],{"type":415,"tag":1322,"props":8172,"children":8176},{"alt":8173,"className":8174,"src":8175},"Astral home website.",[1326,1327],"/posts/images/astral_app_1.png",[],{"type":415,"tag":416,"props":8178,"children":8179},{},[8180],{"type":420,"value":8181},"It solves the issues I was previously pointing out by allowing us to add tags to our starred repositories. That means I can create a tag with a meaningful name and add this tag to the appropriate repositories.",{"type":415,"tag":416,"props":8183,"children":8184},{},[8185],{"type":420,"value":8186},"Once grouped by tags, it becomes very easy to find the repositories you need. The interface is quite simple but shows all the information we need:",{"type":415,"tag":493,"props":8188,"children":8189},{},[8190,8195,8200,8205],{"type":415,"tag":497,"props":8191,"children":8192},{},[8193],{"type":420,"value":8194},"the list of starred repositories grouped by tags",{"type":415,"tag":497,"props":8196,"children":8197},{},[8198],{"type":420,"value":8199},"basic information for each repository (language, number of stars, number of pull requests, number of days since the last commit, the latest release, ...)",{"type":415,"tag":497,"props":8201,"children":8202},{},[8203],{"type":420,"value":8204},"a preview of the README file of the currently selected repository with the URL to quickly clone the repository",{"type":415,"tag":497,"props":8206,"children":8207},{},[8208],{"type":420,"value":8209},"a notes section where you can write extra information for each repository",{"type":415,"tag":416,"props":8211,"children":8212},{},[8213],{"type":415,"tag":1322,"props":8214,"children":8218},{"alt":8215,"className":8216,"src":8217},"List of starred repositories in Astral.",[1326,1327],"/posts/images/astral_app_2.png",[],{"type":415,"tag":588,"props":8220,"children":8221},{"icon":590},[8222],{"type":415,"tag":416,"props":8223,"children":8224},{},[8225],{"type":420,"value":8226},"Astral can also be self-hosted if you need. As it is a free web application, I don't see the point of self-hosting it but it is possible.",{"type":415,"tag":443,"props":8228,"children":8230},{"id":8229},"how-do-i-use-astral",[8231],{"type":420,"value":8232},"How do I use Astral?",{"type":415,"tag":416,"props":8234,"children":8235},{},[8236],{"type":420,"value":8237},"I organize my starred repositories by grouping them with tags that I create in Astral and that are relevant to my way of categorizing things. That is the power of creating custom tags and assigning repositories to them, everyone can organize its starred repositories as it suits him or her.",{"type":415,"tag":416,"props":8239,"children":8240},{},[8241],{"type":420,"value":8242},"To be honest, I only started to use Astral recently so my organization is subject to change but here are kinds of tags I like to create:",{"type":415,"tag":493,"props":8244,"children":8245},{},[8246,8251,8256],{"type":415,"tag":497,"props":8247,"children":8248},{},[8249],{"type":420,"value":8250},"Tags for different IT areas (like database, cloud, testing, IoT,..)",{"type":415,"tag":497,"props":8252,"children":8253},{},[8254],{"type":420,"value":8255},"Tags for the type of repository (like libraries, training, samples)",{"type":415,"tag":497,"props":8257,"children":8258},{},[8259],{"type":420,"value":8260},"Tags for the different frameworks I am interested in",{"type":415,"tag":588,"props":8262,"children":8263},{"icon":590},[8264],{"type":415,"tag":416,"props":8265,"children":8266},{},[8267],{"type":420,"value":8268},"The language used in a repository is one thing but that does not say what framework is used (like ASP.NET Core, MAUI, Vue.js, React.js,...) and that can be a useful criterion when you are later looking for repositories using a specific framework",{"type":415,"tag":416,"props":8270,"children":8271},{},[8272],{"type":415,"tag":1322,"props":8273,"children":8277},{"alt":8274,"className":8275,"src":8276},"Astral website with tags.",[1326,1327],"/posts/images/astral_app_3.png",[],{"type":415,"tag":416,"props":8279,"children":8280},{},[8281],{"type":420,"value":8282},"Starred repositories I have not yet tagged are located in the top left corner of the application. I try to tag a starred repository with different tags to help the future me looking for some repositories with multiple criteria.",{"type":415,"tag":416,"props":8284,"children":8285},{},[8286,8288,8294,8296,8302],{"type":420,"value":8287},"Speaking of multiple criteria, you can create ",{"type":415,"tag":550,"props":8289,"children":8291},{"className":8290},[],[8292],{"type":420,"value":8293},"Smart Filters",{"type":420,"value":8295}," in Astral to filter your starred repositories with specific rules. For instance, I created a smart filter called ",{"type":415,"tag":550,"props":8297,"children":8299},{"className":8298},[],[8300],{"type":420,"value":8301},"Microsoft 365 samples",{"type":420,"value":8303}," to list active repositories showing samples to build Microsoft 365 applications (Teams applications, applications using Microsoft Graph...). This is just an example but you can create much more complex filters.",{"type":415,"tag":416,"props":8305,"children":8306},{},[8307],{"type":415,"tag":1322,"props":8308,"children":8312},{"alt":8309,"className":8310,"src":8311},"Smart filters in Astral.",[1326,1327],"/posts/images/astral_app_4.png",[],{"type":415,"tag":443,"props":8314,"children":8315},{"id":4574},[8316],{"type":420,"value":4577},{"type":415,"tag":416,"props":8318,"children":8319},{},[8320,8322,8328],{"type":420,"value":8321},"If you are often bookmarking GitHub repositories for future usage, Astral is a nice little web application to add to your toolbox. I found it by chance, but I liked it and I am now regularly using it. So try it and if you like it, give it a star and tag the repo with a ",{"type":415,"tag":550,"props":8323,"children":8325},{"className":8324},[],[8326],{"type":420,"value":8327},"tools",{"type":420,"value":8329}," tag in Astral 😉.",{"title":401,"searchDepth":796,"depth":796,"links":8331},[8332,8333,8334,8335,8336],{"id":8058,"depth":796,"text":8061},{"id":8114,"depth":796,"text":8117},{"id":8153,"depth":796,"text":8156},{"id":8229,"depth":796,"text":8232},{"id":4574,"depth":796,"text":4577},"content:1.posts:29.astral.md","1.posts/29.astral.md",{"_path":70,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":69,"description":8340,"lead":8341,"date":8342,"image":8343,"badge":8345,"tags":8347,"body":8348,"_type":1435,"_id":9668,"_source":1437,"_file":9669,"_extension":1439},"In my previous articles about winget I talked about installing packages but I did not talk about producing packages for Windows Package Manager. So let's set things right.","Automate the upgrade of a winget package with GitHub Actions and Winget Create.","2021-08-25T00:00:00.000Z",{"src":8344},"/images/surface_2.jpg",{"label":8346},"Development",[293,304,296,206,243,307],{"type":412,"children":8349,"toc":9646},[8350,8364,8370,8384,8393,8404,8409,8415,8421,8449,8478,8490,8503,8514,8527,8538,8543,8549,8563,8568,8574,8580,8589,8594,8599,8605,8615,8629,8638,8651,8660,8673,8679,8699,8717,8722,8865,8870,8883,8888,8956,8976,9073,9085,9140,9145,9290,9299,9318,9327,9332,9341,9347,9360,9581,9586,9595,9601,9606,9611,9642],{"type":415,"tag":416,"props":8351,"children":8352},{},[8353,8355,8362],{"type":420,"value":8354},"In my ",{"type":415,"tag":423,"props":8356,"children":8359},{"href":8357,"rel":8358},"https://www.techwatching.dev/tags/winget",[427],[8360],{"type":420,"value":8361},"previous articles about winget",{"type":420,"value":8363}," I talked about installing packages but I did not talk about producing packages for Windows Package Manager. So let's set things right.",{"type":415,"tag":443,"props":8365,"children":8367},{"id":8366},"about-winget-packages",[8368],{"type":420,"value":8369},"About winget packages",{"type":415,"tag":416,"props":8371,"children":8372},{},[8373,8375,8382],{"type":420,"value":8374},"Windows Package Manager allows you to search and install applications that are referenced by the sources you have configured to be used by the winget tool. Sources are repositories that list applications that can be installed by winget and the data needed for them to be installed (in the form of a manifest file containing information such as the installer location of a package for instance). The default source is the ",{"type":415,"tag":423,"props":8376,"children":8379},{"href":8377,"rel":8378},"https://github.com/microsoft/winget-pkgs",[427],[8380],{"type":420,"value":8381},"Windows Package Manager Community Repository",{"type":420,"value":8383}," which is a public GitHub repository where everyone can submit their application package manifest to make an application available for installation to Windows Package Manager users.",{"type":415,"tag":416,"props":8385,"children":8386},{},[8387],{"type":415,"tag":1322,"props":8388,"children":8392},{"alt":8389,"className":8390,"src":8391},"Windows Package Manager Community repository readme.",[1326,1327],"/posts/images/wingetcreate_package_repository.png",[],{"type":415,"tag":416,"props":8394,"children":8395},{},[8396,8398],{"type":420,"value":8397},"Once you know that, if you are the developer of an application you want to distribute on Windows through the Windows Package Manager you have to create a manifest for your application and publish it through a Pull Request on the Windows Package Manager Community Repository. And each time you release a new version of your application, you have to update your app manifest with the information of your new package version (new version number, new installer location...) and create a PR to the Windows Package Manager Community Repository with this updated version of your manifest. For more details, you can have a look at the official ",{"type":415,"tag":423,"props":8399,"children":8402},{"href":8400,"rel":8401},"https://docs.microsoft.com/en-us/windows/package-manager/package/",[427],[8403],{"type":420,"value":2391},{"type":415,"tag":416,"props":8405,"children":8406},{},[8407],{"type":420,"value":8408},"As a package creator, you probably do not want to create and update this app manifest manually. Luckily for you, there is a tool to do that for you.",{"type":415,"tag":443,"props":8410,"children":8412},{"id":8411},"wingetcreate-to-the-rescue",[8413],{"type":420,"value":8414},"WingetCreate to the rescue",{"type":415,"tag":616,"props":8416,"children":8418},{"id":8417},"introducing-wingetcreate",[8419],{"type":420,"value":8420},"Introducing WingetCreate",{"type":415,"tag":416,"props":8422,"children":8423},{},[8424,8431,8433,8440,8442,8448],{"type":415,"tag":423,"props":8425,"children":8428},{"href":8426,"rel":8427},"https://github.com/microsoft/winget-create",[427],[8429],{"type":420,"value":8430},"Windows Package Manager Manifest Creator",{"type":420,"value":8432}," aka WingetCreate is a tool \"designed to help generate or update manifest files for the Community repo\" (quoting the readme of WingetCreate repository). At the time of writing it is still in preview but you can already use it to help you with your manifest files. You can download the installer from ",{"type":415,"tag":423,"props":8434,"children":8437},{"href":8435,"rel":8436},"https://aka.ms/wingetcreate/latest",[427],[8438],{"type":420,"value":8439},"this link",{"type":420,"value":8441}," but of course, it is available from winget: ",{"type":415,"tag":550,"props":8443,"children":8445},{"className":8444},[],[8446],{"type":420,"value":8447},"winget install wingetcreate",{"type":420,"value":755},{"type":415,"tag":416,"props":8450,"children":8451},{},[8452,8454,8461,8462,8469,8470,8477],{"type":420,"value":8453},"The main commands are ",{"type":415,"tag":423,"props":8455,"children":8458},{"href":8456,"rel":8457},"https://github.com/microsoft/winget-create/blob/main/doc/new.md",[427],[8459],{"type":420,"value":8460},"New",{"type":420,"value":2505},{"type":415,"tag":423,"props":8463,"children":8466},{"href":8464,"rel":8465},"https://github.com/microsoft/winget-create/blob/main/doc/update.md",[427],[8467],{"type":420,"value":8468},"Update",{"type":420,"value":3487},{"type":415,"tag":423,"props":8471,"children":8474},{"href":8472,"rel":8473},"https://github.com/microsoft/winget-create/blob/main/doc/submit.md",[427],[8475],{"type":420,"value":8476},"Submit",{"type":420,"value":755},{"type":415,"tag":616,"props":8479,"children":8481},{"id":8480},"the-new-command",[8482,8483,8488],{"type":420,"value":3261},{"type":415,"tag":550,"props":8484,"children":8486},{"className":8485},[],[8487],{"type":420,"value":8460},{"type":420,"value":8489}," command",{"type":415,"tag":416,"props":8491,"children":8492},{},[8493,8495,8501],{"type":420,"value":8494},"It allows you to create a new manifest from scratch. If you don't know where to start to deal with manifest files it is a nice way of getting started. Yet having a look at existing manifests in the ",{"type":415,"tag":423,"props":8496,"children":8498},{"href":8377,"rel":8497},[427],[8499],{"type":420,"value":8500},"winget community repository",{"type":420,"value":8502}," can be sometimes more efficient.",{"type":415,"tag":616,"props":8504,"children":8506},{"id":8505},"the-update-command",[8507,8508,8513],{"type":420,"value":3261},{"type":415,"tag":550,"props":8509,"children":8511},{"className":8510},[],[8512],{"type":420,"value":8468},{"type":420,"value":8489},{"type":415,"tag":416,"props":8515,"children":8516},{},[8517,8519,8525],{"type":420,"value":8518},"It allows you to update an existing manifest, that is to say, to create an updated version of your manifest when you have released a new version of your application (so new version number and new installer URL). You can use this command to ",{"type":415,"tag":550,"props":8520,"children":8522},{"className":8521},[],[8523],{"type":420,"value":8524},"submit",{"type":420,"value":8526}," your updated package to the Windows Package Manager Community Repository. In my opinion, it is the most useful command from WingetCreate as it can be easily be integrated into a build pipeline to publish your installer.",{"type":415,"tag":616,"props":8528,"children":8530},{"id":8529},"the-submit-command",[8531,8532,8537],{"type":420,"value":3261},{"type":415,"tag":550,"props":8533,"children":8535},{"className":8534},[],[8536],{"type":420,"value":8476},{"type":420,"value":8489},{"type":415,"tag":416,"props":8539,"children":8540},{},[8541],{"type":420,"value":8542},"It allows you to submit an existing manifest (you created earlier on disk with the create or update command) to the Windows Package Manager Community Repository automatically. Basically, what it does is that it uses the GitHub personal access token you give it to create a Pull Request with your manifest in this repository.",{"type":415,"tag":616,"props":8544,"children":8546},{"id":8545},"what-else",[8547],{"type":420,"value":8548},"What else?",{"type":415,"tag":416,"props":8550,"children":8551},{},[8552,8554,8561],{"type":420,"value":8553},"If you look at the ",{"type":415,"tag":423,"props":8555,"children":8558},{"href":8556,"rel":8557},"https://github.com/microsoft/winget-create/blob/main/doc/settings.md",[427],[8559],{"type":420,"value":8560},"settings command",{"type":420,"value":8562}," you will see that you can specify the name of the GitHub repository to target for your package submission. This is interesting if you want to host a private source for winget available to your organization only where you will publish applications related to your business needs and that you don't want to make available publicly.",{"type":415,"tag":416,"props":8564,"children":8565},{},[8566],{"type":420,"value":8567},"WingetCreate is a really helpful tool to create, update and validate a manifest for your winget package. Still, you probably don't want to manually run WingetCreate each time you release a new package version. So let's see how to automate that with GitHub Actions.",{"type":415,"tag":443,"props":8569,"children":8571},{"id":8570},"automating-your-app-manifest-upgrade-with-github-actions",[8572],{"type":420,"value":8573},"Automating your app manifest upgrade with GitHub Actions",{"type":415,"tag":616,"props":8575,"children":8577},{"id":8576},"why-using-github-actions-to-demonstrate-the-automation-of-app-manifests-upgrades",[8578],{"type":420,"value":8579},"Why using GitHub Actions to demonstrate the automation of app manifests upgrades?",{"type":415,"tag":416,"props":8581,"children":8582},{},[8583],{"type":415,"tag":1322,"props":8584,"children":8588},{"alt":8585,"className":8586,"src":8587},"GitHub Actions documentation.",[1326,1327],"/posts/images/wingetcreate_githubactions.png",[],{"type":415,"tag":416,"props":8590,"children":8591},{},[8592],{"type":420,"value":8593},"In my daily work, Azure Pipelines are the pipelines I used to do CI/CD and they are great. Currently, they offer more functionalities than GitHub Actions and as the code I develop is hosted in Azure Repos it makes more sense to use the Azure DevOps built-in CI/CD tool than something else (although Azure DevOps does not enforce at all you to choose their tools). However there is already in WingetCreate's readme a section with a link to an example about using WingetCreate with Azure Pipelines, but there is no example with GitHub Actions.",{"type":415,"tag":416,"props":8595,"children":8596},{},[8597],{"type":420,"value":8598},"Moreover, I think many applications that are available or will want to be available as a winget package are open source applications whose code are hosted in a GitHub repository and that are already using GitHub Actions for their CI/CD. So I thought it could be useful to have an example of using WingetCreate with GitHub Actions, especially as GitHub has this concept of \"releases\".",{"type":415,"tag":616,"props":8600,"children":8602},{"id":8601},"an-interesting-use-case-for-with-nushell",[8603],{"type":420,"value":8604},"An interesting use case for with Nushell",{"type":415,"tag":416,"props":8606,"children":8607},{},[8608,8613],{"type":415,"tag":423,"props":8609,"children":8611},{"href":3710,"rel":8610},[427],[8612],{"type":420,"value":3714},{"type":420,"value":8614}," is a cross-platform shell written in Rust. Nushell's developers took the best of existing shells (like the structured data approach from PowerShell) and created a shell that feels modern, easy-to-use, and very useful in my opinion.",{"type":415,"tag":416,"props":8616,"children":8617},{},[8618,8620,8627],{"type":420,"value":8619},"There was a ",{"type":415,"tag":423,"props":8621,"children":8624},{"href":8622,"rel":8623},"https://github.com/nushell/nushell/issues/1859",[427],[8625],{"type":420,"value":8626},"GitHub issue",{"type":420,"value":8628}," to support the new official Windows package manager so I though it was the opportunity to contribute to Nushell. Contributing to this project was something that I had not been able to do yet because I did not know Rust, writing CI/CD pipelines however is something I can do.",{"type":415,"tag":416,"props":8630,"children":8631},{},[8632],{"type":415,"tag":1322,"props":8633,"children":8637},{"alt":8634,"className":8635,"src":8636},"Nushell documentation page.",[1326,1327],"/posts/images/wingetcreate_nushell.png",[],{"type":415,"tag":416,"props":8639,"children":8640},{},[8641,8643,8649],{"type":420,"value":8642},"Nushell already uses GitHub Actions for its continuous integration and to create releases. If you are not familiar with GitHub releases you can read the ",{"type":415,"tag":423,"props":8644,"children":8647},{"href":8645,"rel":8646},"https://docs.github.com/en/github/administering-a-repository/releasing-projects-on-github/about-releases",[427],[8648],{"type":420,"value":7617},{"type":420,"value":8650}," but basically a release is a version of your software (corresponding to a git tag in your repository) that you make available with release notes and binaries files.",{"type":415,"tag":416,"props":8652,"children":8653},{},[8654],{"type":415,"tag":1322,"props":8655,"children":8659},{"alt":8656,"className":8657,"src":8658},"Nushell release page in GitHub.",[1326,1327],"/posts/images/wingetcreate_release.png",[],{"type":415,"tag":416,"props":8661,"children":8662},{},[8663,8665,8671],{"type":420,"value":8664},"Therefore, the idea was to update Nushell manifest with the latest version of Nushell using ",{"type":415,"tag":550,"props":8666,"children":8668},{"className":8667},[],[8669],{"type":420,"value":8670},"WingetCreate",{"type":420,"value":8672}," each time a new release of Nushell is published.",{"type":415,"tag":616,"props":8674,"children":8676},{"id":8675},"triggering-a-new-workflow-from-a-release-event",[8677],{"type":420,"value":8678},"Triggering a new workflow from a release event",{"type":415,"tag":416,"props":8680,"children":8681},{},[8682,8684,8690,8692,8697],{"type":420,"value":8683},"Automating the app manifest upgrade of Nushell just meant creating a ",{"type":415,"tag":550,"props":8685,"children":8687},{"className":8686},[],[8688],{"type":420,"value":8689},"job",{"type":420,"value":8691}," in a GitHub Actions workflow that would call ",{"type":415,"tag":550,"props":8693,"children":8695},{"className":8694},[],[8696],{"type":420,"value":8670},{"type":420,"value":8698}," with the new version number and the new installer URL.",{"type":415,"tag":416,"props":8700,"children":8701},{},[8702,8704,8709,8711,8716],{"type":420,"value":8703},"I first wanted to modify the existing Nushell GitHub Actions workflow that was creating the releases by adding a new ",{"type":415,"tag":550,"props":8705,"children":8707},{"className":8706},[],[8708],{"type":420,"value":8689},{"type":420,"value":8710}," at the end of the workflow just after the release was created. Well this is was a bad idea, I pushed this change and during the next release of Nushell the workflow failed because I did not pay attention that the workflow was creating releases in draft, so the installer URL of the new version did not exist when my job called ",{"type":415,"tag":550,"props":8712,"children":8714},{"className":8713},[],[8715],{"type":420,"value":8670},{"type":420,"value":755},{"type":415,"tag":416,"props":8718,"children":8719},{},[8720],{"type":420,"value":8721},"Because of that, I decided to create a separate workflow that would be triggered each time a Nushell release is published. In Nushell this is done manually (passing from draft to release) but even if it were done automatically by the release workflow I think it is a better idea to have a specific workflow triggered by the publication of a release.",{"type":415,"tag":773,"props":8723,"children":8727},{"className":8724,"code":8725,"language":8726,"meta":401,"style":401},"language-yml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","name: Submit Nushell package to Windows Package Manager Community Repository \n\non:\n  release:\n    types: [published]\n\njobs:\n\n  winget:\n    name: Publish winget package\n","yml",[8728],{"type":415,"tag":550,"props":8729,"children":8730},{"__ignoreMap":401},[8731,8753,8760,8772,8784,8810,8817,8829,8836,8848],{"type":415,"tag":783,"props":8732,"children":8733},{"class":785,"line":786},[8734,8739,8743,8748],{"type":415,"tag":783,"props":8735,"children":8736},{"style":929},[8737],{"type":420,"value":8738},"name",{"type":415,"tag":783,"props":8740,"children":8741},{"style":790},[8742],{"type":420,"value":635},{"type":415,"tag":783,"props":8744,"children":8745},{"style":825},[8746],{"type":420,"value":8747}," Submit Nushell package to Windows Package Manager Community Repository",{"type":415,"tag":783,"props":8749,"children":8750},{"style":1362},[8751],{"type":420,"value":8752}," \n",{"type":415,"tag":783,"props":8754,"children":8755},{"class":785,"line":796},[8756],{"type":415,"tag":783,"props":8757,"children":8758},{"emptyLinePlaceholder":5750},[8759],{"type":420,"value":5753},{"type":415,"tag":783,"props":8761,"children":8762},{"class":785,"line":840},[8763,8768],{"type":415,"tag":783,"props":8764,"children":8765},{"style":5736},[8766],{"type":420,"value":8767},"on",{"type":415,"tag":783,"props":8769,"children":8770},{"style":790},[8771],{"type":420,"value":4960},{"type":415,"tag":783,"props":8773,"children":8774},{"class":785,"line":866},[8775,8780],{"type":415,"tag":783,"props":8776,"children":8777},{"style":929},[8778],{"type":420,"value":8779},"  release",{"type":415,"tag":783,"props":8781,"children":8782},{"style":790},[8783],{"type":420,"value":4960},{"type":415,"tag":783,"props":8785,"children":8786},{"class":785,"line":893},[8787,8792,8796,8801,8806],{"type":415,"tag":783,"props":8788,"children":8789},{"style":929},[8790],{"type":420,"value":8791},"    types",{"type":415,"tag":783,"props":8793,"children":8794},{"style":790},[8795],{"type":420,"value":635},{"type":415,"tag":783,"props":8797,"children":8798},{"style":790},[8799],{"type":420,"value":8800}," [",{"type":415,"tag":783,"props":8802,"children":8803},{"style":825},[8804],{"type":420,"value":8805},"published",{"type":415,"tag":783,"props":8807,"children":8808},{"style":790},[8809],{"type":420,"value":6800},{"type":415,"tag":783,"props":8811,"children":8812},{"class":785,"line":920},[8813],{"type":415,"tag":783,"props":8814,"children":8815},{"emptyLinePlaceholder":5750},[8816],{"type":420,"value":5753},{"type":415,"tag":783,"props":8818,"children":8819},{"class":785,"line":959},[8820,8825],{"type":415,"tag":783,"props":8821,"children":8822},{"style":929},[8823],{"type":420,"value":8824},"jobs",{"type":415,"tag":783,"props":8826,"children":8827},{"style":790},[8828],{"type":420,"value":4960},{"type":415,"tag":783,"props":8830,"children":8831},{"class":785,"line":997},[8832],{"type":415,"tag":783,"props":8833,"children":8834},{"emptyLinePlaceholder":5750},[8835],{"type":420,"value":5753},{"type":415,"tag":783,"props":8837,"children":8838},{"class":785,"line":1023},[8839,8844],{"type":415,"tag":783,"props":8840,"children":8841},{"style":929},[8842],{"type":420,"value":8843},"  winget",{"type":415,"tag":783,"props":8845,"children":8846},{"style":790},[8847],{"type":420,"value":4960},{"type":415,"tag":783,"props":8849,"children":8850},{"class":785,"line":1061},[8851,8856,8860],{"type":415,"tag":783,"props":8852,"children":8853},{"style":929},[8854],{"type":420,"value":8855},"    name",{"type":415,"tag":783,"props":8857,"children":8858},{"style":790},[8859],{"type":420,"value":635},{"type":415,"tag":783,"props":8861,"children":8862},{"style":825},[8863],{"type":420,"value":8864}," Publish winget package\n",{"type":415,"tag":416,"props":8866,"children":8867},{},[8868],{"type":420,"value":8869},"I like how it is possible with GitHub Actions to trigger on many different GitHub events. It is something that seems more limited in Azure Pipelines.",{"type":415,"tag":616,"props":8871,"children":8873},{"id":8872},"calling-wingetcreate-from-a-github-actions-workflow",[8874,8876,8881],{"type":420,"value":8875},"Calling ",{"type":415,"tag":550,"props":8877,"children":8879},{"className":8878},[],[8880],{"type":420,"value":8670},{"type":420,"value":8882}," from a GitHub Actions workflow.",{"type":415,"tag":416,"props":8884,"children":8885},{},[8886],{"type":420,"value":8887},"Windows Package Manager Manifest Creator needs to be run in windows so we need to specify that in the job that will submit a new version of Nushell package to Windows Package Manager Community Repository:",{"type":415,"tag":773,"props":8889,"children":8891},{"className":8724,"code":8890,"language":8726,"meta":401,"style":401},"jobs:\n\n  winget:\n    name: Publish winget package\n    runs-on: windows-latest\n",[8892],{"type":415,"tag":550,"props":8893,"children":8894},{"__ignoreMap":401},[8895,8906,8913,8924,8939],{"type":415,"tag":783,"props":8896,"children":8897},{"class":785,"line":786},[8898,8902],{"type":415,"tag":783,"props":8899,"children":8900},{"style":929},[8901],{"type":420,"value":8824},{"type":415,"tag":783,"props":8903,"children":8904},{"style":790},[8905],{"type":420,"value":4960},{"type":415,"tag":783,"props":8907,"children":8908},{"class":785,"line":796},[8909],{"type":415,"tag":783,"props":8910,"children":8911},{"emptyLinePlaceholder":5750},[8912],{"type":420,"value":5753},{"type":415,"tag":783,"props":8914,"children":8915},{"class":785,"line":840},[8916,8920],{"type":415,"tag":783,"props":8917,"children":8918},{"style":929},[8919],{"type":420,"value":8843},{"type":415,"tag":783,"props":8921,"children":8922},{"style":790},[8923],{"type":420,"value":4960},{"type":415,"tag":783,"props":8925,"children":8926},{"class":785,"line":866},[8927,8931,8935],{"type":415,"tag":783,"props":8928,"children":8929},{"style":929},[8930],{"type":420,"value":8855},{"type":415,"tag":783,"props":8932,"children":8933},{"style":790},[8934],{"type":420,"value":635},{"type":415,"tag":783,"props":8936,"children":8937},{"style":825},[8938],{"type":420,"value":8864},{"type":415,"tag":783,"props":8940,"children":8941},{"class":785,"line":893},[8942,8947,8951],{"type":415,"tag":783,"props":8943,"children":8944},{"style":929},[8945],{"type":420,"value":8946},"    runs-on",{"type":415,"tag":783,"props":8948,"children":8949},{"style":790},[8950],{"type":420,"value":635},{"type":415,"tag":783,"props":8952,"children":8953},{"style":825},[8954],{"type":420,"value":8955}," windows-latest\n",{"type":415,"tag":416,"props":8957,"children":8958},{},[8959,8961,8966,8968,8974],{"type":420,"value":8960},"This job will only contain one step that is the execution of the commands to call ",{"type":415,"tag":550,"props":8962,"children":8964},{"className":8963},[],[8965],{"type":420,"value":8670},{"type":420,"value":8967},". These commands will be in PowerShell as this is the default runner (",{"type":415,"tag":550,"props":8969,"children":8971},{"className":8970},[],[8972],{"type":420,"value":8973},"pwsh",{"type":420,"value":8975},") in a windows job.",{"type":415,"tag":773,"props":8977,"children":8979},{"className":8724,"code":8978,"language":8726,"meta":401,"style":401},"  winget:\n    name: Publish winget package\n    runs-on: windows-latest\n    steps:\n      - name: Submit package to Windows Package Manager Community Repository\n        run: |\n\n",[8980],{"type":415,"tag":550,"props":8981,"children":8982},{"__ignoreMap":401},[8983,8994,9009,9024,9036,9057],{"type":415,"tag":783,"props":8984,"children":8985},{"class":785,"line":786},[8986,8990],{"type":415,"tag":783,"props":8987,"children":8988},{"style":929},[8989],{"type":420,"value":8843},{"type":415,"tag":783,"props":8991,"children":8992},{"style":790},[8993],{"type":420,"value":4960},{"type":415,"tag":783,"props":8995,"children":8996},{"class":785,"line":796},[8997,9001,9005],{"type":415,"tag":783,"props":8998,"children":8999},{"style":929},[9000],{"type":420,"value":8855},{"type":415,"tag":783,"props":9002,"children":9003},{"style":790},[9004],{"type":420,"value":635},{"type":415,"tag":783,"props":9006,"children":9007},{"style":825},[9008],{"type":420,"value":8864},{"type":415,"tag":783,"props":9010,"children":9011},{"class":785,"line":840},[9012,9016,9020],{"type":415,"tag":783,"props":9013,"children":9014},{"style":929},[9015],{"type":420,"value":8946},{"type":415,"tag":783,"props":9017,"children":9018},{"style":790},[9019],{"type":420,"value":635},{"type":415,"tag":783,"props":9021,"children":9022},{"style":825},[9023],{"type":420,"value":8955},{"type":415,"tag":783,"props":9025,"children":9026},{"class":785,"line":866},[9027,9032],{"type":415,"tag":783,"props":9028,"children":9029},{"style":929},[9030],{"type":420,"value":9031},"    steps",{"type":415,"tag":783,"props":9033,"children":9034},{"style":790},[9035],{"type":420,"value":4960},{"type":415,"tag":783,"props":9037,"children":9038},{"class":785,"line":893},[9039,9044,9048,9052],{"type":415,"tag":783,"props":9040,"children":9041},{"style":790},[9042],{"type":420,"value":9043},"      -",{"type":415,"tag":783,"props":9045,"children":9046},{"style":929},[9047],{"type":420,"value":5525},{"type":415,"tag":783,"props":9049,"children":9050},{"style":790},[9051],{"type":420,"value":635},{"type":415,"tag":783,"props":9053,"children":9054},{"style":825},[9055],{"type":420,"value":9056}," Submit package to Windows Package Manager Community Repository\n",{"type":415,"tag":783,"props":9058,"children":9059},{"class":785,"line":920},[9060,9065,9069],{"type":415,"tag":783,"props":9061,"children":9062},{"style":929},[9063],{"type":420,"value":9064},"        run",{"type":415,"tag":783,"props":9066,"children":9067},{"style":790},[9068],{"type":420,"value":635},{"type":415,"tag":783,"props":9070,"children":9071},{"style":1356},[9072],{"type":420,"value":5401},{"type":415,"tag":416,"props":9074,"children":9075},{},[9076,9078,9083],{"type":420,"value":9077},"First, we need to download the latest version of ",{"type":415,"tag":550,"props":9079,"children":9081},{"className":9080},[],[9082],{"type":420,"value":8670},{"type":420,"value":9084}," by using the following command :",{"type":415,"tag":773,"props":9086,"children":9088},{"className":2236,"code":9087,"language":248,"meta":401,"style":401},"iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe\n",[9089],{"type":415,"tag":550,"props":9090,"children":9091},{"__ignoreMap":401},[9092],{"type":415,"tag":783,"props":9093,"children":9094},{"class":785,"line":786},[9095,9100,9104,9109,9113,9117,9121,9126,9130,9135],{"type":415,"tag":783,"props":9096,"children":9097},{"style":1362},[9098],{"type":420,"value":9099},"iwr https:",{"type":415,"tag":783,"props":9101,"children":9102},{"style":790},[9103],{"type":420,"value":3406},{"type":415,"tag":783,"props":9105,"children":9106},{"style":1362},[9107],{"type":420,"value":9108},"aka.ms",{"type":415,"tag":783,"props":9110,"children":9111},{"style":790},[9112],{"type":420,"value":3417},{"type":415,"tag":783,"props":9114,"children":9115},{"style":1362},[9116],{"type":420,"value":304},{"type":415,"tag":783,"props":9118,"children":9119},{"style":790},[9120],{"type":420,"value":3417},{"type":415,"tag":783,"props":9122,"children":9123},{"style":1362},[9124],{"type":420,"value":9125},"latest ",{"type":415,"tag":783,"props":9127,"children":9128},{"style":790},[9129],{"type":420,"value":3356},{"type":415,"tag":783,"props":9131,"children":9132},{"style":1362},[9133],{"type":420,"value":9134},"OutFile ",{"type":415,"tag":783,"props":9136,"children":9137},{"style":3409},[9138],{"type":420,"value":9139},"wingetcreate.exe\n",{"type":415,"tag":416,"props":9141,"children":9142},{},[9143],{"type":420,"value":9144},"Second, we want to retrieve the version number and the installer URL of the new package. These 2 pieces of information will be needed as parameters to the WingetCreate update command. We can find these in the GitHub context which contains the release event that triggered the workflow. We are using these 2 lines of PowerShell to get assets associated with the release and filter on the msi file which is the Windows installer of Nushell.",{"type":415,"tag":773,"props":9146,"children":9148},{"className":2236,"code":9147,"language":248,"meta":401,"style":401},"$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json\n$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1\n",[9149],{"type":415,"tag":550,"props":9150,"children":9151},{"__ignoreMap":401},[9152,9197],{"type":415,"tag":783,"props":9153,"children":9154},{"class":785,"line":786},[9155,9159,9164,9168,9173,9178,9183,9188,9192],{"type":415,"tag":783,"props":9156,"children":9157},{"style":790},[9158],{"type":420,"value":4176},{"type":415,"tag":783,"props":9160,"children":9161},{"style":1362},[9162],{"type":420,"value":9163},"github ",{"type":415,"tag":783,"props":9165,"children":9166},{"style":790},[9167],{"type":420,"value":1993},{"type":415,"tag":783,"props":9169,"children":9170},{"style":3409},[9171],{"type":420,"value":9172}," Get-Content",{"type":415,"tag":783,"props":9174,"children":9175},{"style":790},[9176],{"type":420,"value":9177}," '",{"type":415,"tag":783,"props":9179,"children":9180},{"style":825},[9181],{"type":420,"value":9182},"${{ github.event_path }}",{"type":415,"tag":783,"props":9184,"children":9185},{"style":790},[9186],{"type":420,"value":9187},"'",{"type":415,"tag":783,"props":9189,"children":9190},{"style":790},[9191],{"type":420,"value":5490},{"type":415,"tag":783,"props":9193,"children":9194},{"style":3409},[9195],{"type":420,"value":9196}," ConvertFrom-Json\n",{"type":415,"tag":783,"props":9198,"children":9199},{"class":785,"line":796},[9200,9204,9209,9213,9217,9222,9226,9231,9235,9240,9245,9249,9254,9258,9262,9267,9271,9276,9280,9285],{"type":415,"tag":783,"props":9201,"children":9202},{"style":790},[9203],{"type":420,"value":4176},{"type":415,"tag":783,"props":9205,"children":9206},{"style":1362},[9207],{"type":420,"value":9208},"installerUrl ",{"type":415,"tag":783,"props":9210,"children":9211},{"style":790},[9212],{"type":420,"value":1993},{"type":415,"tag":783,"props":9214,"children":9215},{"style":790},[9216],{"type":420,"value":4197},{"type":415,"tag":783,"props":9218,"children":9219},{"style":1362},[9220],{"type":420,"value":9221},"github.release.assets ",{"type":415,"tag":783,"props":9223,"children":9224},{"style":790},[9225],{"type":420,"value":4216},{"type":415,"tag":783,"props":9227,"children":9228},{"style":3409},[9229],{"type":420,"value":9230}," Where-Object",{"type":415,"tag":783,"props":9232,"children":9233},{"style":790},[9234],{"type":420,"value":4234},{"type":415,"tag":783,"props":9236,"children":9237},{"style":1362},[9238],{"type":420,"value":9239},"Property name ",{"type":415,"tag":783,"props":9241,"children":9242},{"style":790},[9243],{"type":420,"value":9244},"-match",{"type":415,"tag":783,"props":9246,"children":9247},{"style":790},[9248],{"type":420,"value":9177},{"type":415,"tag":783,"props":9250,"children":9251},{"style":825},[9252],{"type":420,"value":9253},"windows.msi",{"type":415,"tag":783,"props":9255,"children":9256},{"style":790},[9257],{"type":420,"value":9187},{"type":415,"tag":783,"props":9259,"children":9260},{"style":790},[9261],{"type":420,"value":5490},{"type":415,"tag":783,"props":9263,"children":9264},{"style":1362},[9265],{"type":420,"value":9266}," Select ",{"type":415,"tag":783,"props":9268,"children":9269},{"style":790},[9270],{"type":420,"value":3356},{"type":415,"tag":783,"props":9272,"children":9273},{"style":1362},[9274],{"type":420,"value":9275},"ExpandProperty browser_download_url ",{"type":415,"tag":783,"props":9277,"children":9278},{"style":790},[9279],{"type":420,"value":3356},{"type":415,"tag":783,"props":9281,"children":9282},{"style":1362},[9283],{"type":420,"value":9284},"First ",{"type":415,"tag":783,"props":9286,"children":9287},{"style":902},[9288],{"type":420,"value":9289},"1\n",{"type":415,"tag":9291,"props":9292,"children":9293},"blockquote",{},[9294],{"type":415,"tag":416,"props":9295,"children":9296},{},[9297],{"type":420,"value":9298},"💡 I just thought that instead of doing this in PowerShell we could have done this in Nushell, which would have been fun 'using Nushell to provide a new version of Nushell' but as it is not installed by default on windows agents it would mean a loss of time each time the workflow runs.",{"type":415,"tag":416,"props":9300,"children":9301},{},[9302,9304,9309,9311,9316],{"type":420,"value":9303},"Third, we can call the ",{"type":415,"tag":550,"props":9305,"children":9307},{"className":9306},[],[9308],{"type":420,"value":8670},{"type":420,"value":9310}," update command by specifying the version, the URL of the installer, and a Personal Access Token that will be used by ",{"type":415,"tag":550,"props":9312,"children":9314},{"className":9313},[],[9315],{"type":420,"value":8670},{"type":420,"value":9317}," to make the Pull Request in the Windows Package Manager Community Repository. This PAT needs to be created by a maintainer of the repository with permission and added to the secrets of the project.",{"type":415,"tag":416,"props":9319,"children":9320},{},[9321],{"type":415,"tag":1322,"props":9322,"children":9326},{"alt":9323,"className":9324,"src":9325},"Screen to define Personal Access Tokens.",[1326,1327],"/posts/images/wingetcreate_pat.png",[],{"type":415,"tag":416,"props":9328,"children":9329},{},[9330],{"type":420,"value":9331},"Here you can see a run of the workflow in GitHub:",{"type":415,"tag":416,"props":9333,"children":9334},{},[9335],{"type":415,"tag":1322,"props":9336,"children":9340},{"alt":9337,"className":9338,"src":9339},"GitHub Actions workflow running.",[1326,1327],"/posts/images/wingetcreate_wokflow_1.png",[],{"type":415,"tag":443,"props":9342,"children":9344},{"id":9343},"overview-of-the-created-workflow",[9345],{"type":420,"value":9346},"Overview of the created workflow",{"type":415,"tag":416,"props":9348,"children":9349},{},[9350,9352,9358],{"type":420,"value":9351},"You can find the complete workflow below and ",{"type":415,"tag":423,"props":9353,"children":9356},{"href":9354,"rel":9355},"https://github.com/nushell/nushell/blob/main/.github/workflows/winget-submission.yml",[427],[9357],{"type":420,"value":2266},{"type":420,"value":9359}," in the Nushell repository.",{"type":415,"tag":773,"props":9361,"children":9363},{"className":8724,"code":9362,"language":8726,"meta":401,"style":401},"name: Submit Nushell package to Windows Package Manager Community Repository \n\non:\n  release:\n    types: [published]\n\njobs:\n\n  winget:\n    name: Publish winget package\n    runs-on: windows-latest\n    steps:\n      - name: Submit package to Windows Package Manager Community Repository\n        run: |\n          iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe\n          $github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json\n          $installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1\n          .\\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}\n",[9364],{"type":415,"tag":550,"props":9365,"children":9366},{"__ignoreMap":401},[9367,9386,9393,9404,9415,9438,9445,9456,9463,9474,9489,9504,9515,9534,9549,9557,9565,9573],{"type":415,"tag":783,"props":9368,"children":9369},{"class":785,"line":786},[9370,9374,9378,9382],{"type":415,"tag":783,"props":9371,"children":9372},{"style":929},[9373],{"type":420,"value":8738},{"type":415,"tag":783,"props":9375,"children":9376},{"style":790},[9377],{"type":420,"value":635},{"type":415,"tag":783,"props":9379,"children":9380},{"style":825},[9381],{"type":420,"value":8747},{"type":415,"tag":783,"props":9383,"children":9384},{"style":1362},[9385],{"type":420,"value":8752},{"type":415,"tag":783,"props":9387,"children":9388},{"class":785,"line":796},[9389],{"type":415,"tag":783,"props":9390,"children":9391},{"emptyLinePlaceholder":5750},[9392],{"type":420,"value":5753},{"type":415,"tag":783,"props":9394,"children":9395},{"class":785,"line":840},[9396,9400],{"type":415,"tag":783,"props":9397,"children":9398},{"style":5736},[9399],{"type":420,"value":8767},{"type":415,"tag":783,"props":9401,"children":9402},{"style":790},[9403],{"type":420,"value":4960},{"type":415,"tag":783,"props":9405,"children":9406},{"class":785,"line":866},[9407,9411],{"type":415,"tag":783,"props":9408,"children":9409},{"style":929},[9410],{"type":420,"value":8779},{"type":415,"tag":783,"props":9412,"children":9413},{"style":790},[9414],{"type":420,"value":4960},{"type":415,"tag":783,"props":9416,"children":9417},{"class":785,"line":893},[9418,9422,9426,9430,9434],{"type":415,"tag":783,"props":9419,"children":9420},{"style":929},[9421],{"type":420,"value":8791},{"type":415,"tag":783,"props":9423,"children":9424},{"style":790},[9425],{"type":420,"value":635},{"type":415,"tag":783,"props":9427,"children":9428},{"style":790},[9429],{"type":420,"value":8800},{"type":415,"tag":783,"props":9431,"children":9432},{"style":825},[9433],{"type":420,"value":8805},{"type":415,"tag":783,"props":9435,"children":9436},{"style":790},[9437],{"type":420,"value":6800},{"type":415,"tag":783,"props":9439,"children":9440},{"class":785,"line":920},[9441],{"type":415,"tag":783,"props":9442,"children":9443},{"emptyLinePlaceholder":5750},[9444],{"type":420,"value":5753},{"type":415,"tag":783,"props":9446,"children":9447},{"class":785,"line":959},[9448,9452],{"type":415,"tag":783,"props":9449,"children":9450},{"style":929},[9451],{"type":420,"value":8824},{"type":415,"tag":783,"props":9453,"children":9454},{"style":790},[9455],{"type":420,"value":4960},{"type":415,"tag":783,"props":9457,"children":9458},{"class":785,"line":997},[9459],{"type":415,"tag":783,"props":9460,"children":9461},{"emptyLinePlaceholder":5750},[9462],{"type":420,"value":5753},{"type":415,"tag":783,"props":9464,"children":9465},{"class":785,"line":1023},[9466,9470],{"type":415,"tag":783,"props":9467,"children":9468},{"style":929},[9469],{"type":420,"value":8843},{"type":415,"tag":783,"props":9471,"children":9472},{"style":790},[9473],{"type":420,"value":4960},{"type":415,"tag":783,"props":9475,"children":9476},{"class":785,"line":1061},[9477,9481,9485],{"type":415,"tag":783,"props":9478,"children":9479},{"style":929},[9480],{"type":420,"value":8855},{"type":415,"tag":783,"props":9482,"children":9483},{"style":790},[9484],{"type":420,"value":635},{"type":415,"tag":783,"props":9486,"children":9487},{"style":825},[9488],{"type":420,"value":8864},{"type":415,"tag":783,"props":9490,"children":9491},{"class":785,"line":1099},[9492,9496,9500],{"type":415,"tag":783,"props":9493,"children":9494},{"style":929},[9495],{"type":420,"value":8946},{"type":415,"tag":783,"props":9497,"children":9498},{"style":790},[9499],{"type":420,"value":635},{"type":415,"tag":783,"props":9501,"children":9502},{"style":825},[9503],{"type":420,"value":8955},{"type":415,"tag":783,"props":9505,"children":9506},{"class":785,"line":1137},[9507,9511],{"type":415,"tag":783,"props":9508,"children":9509},{"style":929},[9510],{"type":420,"value":9031},{"type":415,"tag":783,"props":9512,"children":9513},{"style":790},[9514],{"type":420,"value":4960},{"type":415,"tag":783,"props":9516,"children":9517},{"class":785,"line":1175},[9518,9522,9526,9530],{"type":415,"tag":783,"props":9519,"children":9520},{"style":790},[9521],{"type":420,"value":9043},{"type":415,"tag":783,"props":9523,"children":9524},{"style":929},[9525],{"type":420,"value":5525},{"type":415,"tag":783,"props":9527,"children":9528},{"style":790},[9529],{"type":420,"value":635},{"type":415,"tag":783,"props":9531,"children":9532},{"style":825},[9533],{"type":420,"value":9056},{"type":415,"tag":783,"props":9535,"children":9536},{"class":785,"line":1213},[9537,9541,9545],{"type":415,"tag":783,"props":9538,"children":9539},{"style":929},[9540],{"type":420,"value":9064},{"type":415,"tag":783,"props":9542,"children":9543},{"style":790},[9544],{"type":420,"value":635},{"type":415,"tag":783,"props":9546,"children":9547},{"style":1356},[9548],{"type":420,"value":5401},{"type":415,"tag":783,"props":9550,"children":9551},{"class":785,"line":1239},[9552],{"type":415,"tag":783,"props":9553,"children":9554},{"style":825},[9555],{"type":420,"value":9556},"          iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe\n",{"type":415,"tag":783,"props":9558,"children":9559},{"class":785,"line":1248},[9560],{"type":415,"tag":783,"props":9561,"children":9562},{"style":825},[9563],{"type":420,"value":9564},"          $github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json\n",{"type":415,"tag":783,"props":9566,"children":9567},{"class":785,"line":1257},[9568],{"type":415,"tag":783,"props":9569,"children":9570},{"style":825},[9571],{"type":420,"value":9572},"          $installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1\n",{"type":415,"tag":783,"props":9574,"children":9575},{"class":785,"line":1266},[9576],{"type":415,"tag":783,"props":9577,"children":9578},{"style":825},[9579],{"type":420,"value":9580},"          .\\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}\n",{"type":415,"tag":416,"props":9582,"children":9583},{},[9584],{"type":420,"value":9585},"Here is what a Pull Request generated by the GitHub Actions workflow looks like:",{"type":415,"tag":416,"props":9587,"children":9588},{},[9589],{"type":415,"tag":1322,"props":9590,"children":9594},{"alt":9591,"className":9592,"src":9593},"Pull Request in the winget-pkgs repository.",[1326,1327],"/posts/images/wingetcreate_pr.png",[],{"type":415,"tag":443,"props":9596,"children":9598},{"id":9597},"to-summarize",[9599],{"type":420,"value":9600},"To summarize",{"type":415,"tag":416,"props":9602,"children":9603},{},[9604],{"type":420,"value":9605},"We have introduced the notion of source for winget packages and in particular, the Windows Package Manager Community Repository where we can open PR to submit a new application or new versions of an existing application. We have seen how Windows Package Manager Manifest Creator could help us do that and how it could be automated from a GitHub Actions workflow like it was done for the Nushell project.",{"type":415,"tag":416,"props":9607,"children":9608},{},[9609],{"type":420,"value":9610},"Do not hesitate to copy some of the GitHub Actions workflows I showed you. I hope this will inspire you to do the same to distribute your applications through winget.",{"type":415,"tag":416,"props":9612,"children":9613},{},[9614,9616,9623,9625,9632,9633,9640],{"type":420,"value":9615},"A big thank you to ",{"type":415,"tag":423,"props":9617,"children":9620},{"href":9618,"rel":9619},"https://twitter.com/ethomson",[427],[9621],{"type":420,"value":9622},"Edward Thomson",{"type":420,"value":9624}," who explained to me how to retrieve GitHub Actions contexts in PowerShell. Thanks also to ",{"type":415,"tag":423,"props":9626,"children":9629},{"href":9627,"rel":9628},"https://twitter.com/fdncred",[427],[9630],{"type":420,"value":9631},"Darren Schroeder",{"type":420,"value":3487},{"type":415,"tag":423,"props":9634,"children":9637},{"href":9635,"rel":9636},"https://twitter.com/jntrnr",[427],[9638],{"type":420,"value":9639},"Jonathan Turner",{"type":420,"value":9641}," who supported me to set up a workflow that publishes new releases of Nushell in winget.",{"type":415,"tag":1420,"props":9643,"children":9644},{},[9645],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":9647},[9648,9649,9659,9666,9667],{"id":8366,"depth":796,"text":8369},{"id":8411,"depth":796,"text":8414,"children":9650},[9651,9652,9654,9656,9658],{"id":8417,"depth":840,"text":8420},{"id":8480,"depth":840,"text":9653},"The New command",{"id":8505,"depth":840,"text":9655},"The Update command",{"id":8529,"depth":840,"text":9657},"The Submit command",{"id":8545,"depth":840,"text":8548},{"id":8570,"depth":796,"text":8573,"children":9660},[9661,9662,9663,9664],{"id":8576,"depth":840,"text":8579},{"id":8601,"depth":840,"text":8604},{"id":8675,"depth":840,"text":8678},{"id":8872,"depth":840,"text":9665},"Calling WingetCreate from a GitHub Actions workflow.",{"id":9343,"depth":796,"text":9346},{"id":9597,"depth":796,"text":9600},"content:1.posts:21.wingetcreate.md","1.posts/21.wingetcreate.md",{"_path":61,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":60,"description":9671,"lead":9672,"date":9673,"image":9674,"badge":9675,"tags":9676,"body":9677,"_type":1435,"_id":9895,"_source":1437,"_file":9896,"_extension":1439},"In my last article about Windows Package Manager, I said that with winget I was missing \"being able to specify some parameters for a package installation (like the workload and components to install for Visual Studio 2019)\". Well, that was before I went through a few GitHub issues of the winget-cli repository that mentioned the override option.","The override option of winget install command","2021-05-31T00:00:00.000Z",{"src":8344},{"label":408},[291,293,296,206],{"type":412,"children":9678,"toc":9888},[9679,9699,9705,9738,9749,9755,9760,9813,9825,9838,9866,9872,9884],{"type":415,"tag":416,"props":9680,"children":9681},{},[9682,9684,9689,9691,9697],{"type":420,"value":9683},"In my last article about Windows Package Manager, I said that with winget I was missing \"",{"type":415,"tag":2746,"props":9685,"children":9686},{},[9687],{"type":420,"value":9688},"being able to specify some parameters for a package installation (like the workload and components to install for Visual Studio 2019)",{"type":420,"value":9690},"\". Well, that was before I went through a few GitHub issues of the winget-cli repository that mentioned the ",{"type":415,"tag":550,"props":9692,"children":9694},{"className":9693},[],[9695],{"type":420,"value":9696},"override",{"type":420,"value":9698}," option.",{"type":415,"tag":443,"props":9700,"children":9702},{"id":9701},"about-the-override-option",[9703],{"type":420,"value":9704},"About the override option",{"type":415,"tag":416,"props":9706,"children":9707},{},[9708,9714,9716,9721,9723,9729,9731,9736],{"type":415,"tag":550,"props":9709,"children":9711},{"className":9710},[],[9712],{"type":420,"value":9713},"winget install",{"type":420,"value":9715}," official documentation says the ",{"type":415,"tag":550,"props":9717,"children":9719},{"className":9718},[],[9720],{"type":420,"value":9696},{"type":420,"value":9722}," option to the ",{"type":415,"tag":550,"props":9724,"children":9726},{"className":9725},[],[9727],{"type":420,"value":9728},"install",{"type":420,"value":9730}," command can be used to provide a \"",{"type":415,"tag":2746,"props":9732,"children":9733},{},[9734],{"type":420,"value":9735},"string that will be passed directly to the installer",{"type":420,"value":9737},"\". What does that mean exactly?",{"type":415,"tag":416,"props":9739,"children":9740},{},[9741,9743,9748],{"type":420,"value":9742},"When you install a program on your laptop, the installer GUI often prompts you to configure some parameters for the program. For instance, Git installer lets you choose how you want to handle CRLF. Each installer has its own parameters that you can specify on the command line if you run the installer exe in your terminal. When using winget CLI, you can also specify these parameters by passing them as the value of the option ",{"type":415,"tag":550,"props":9744,"children":9746},{"className":9745},[],[9747],{"type":420,"value":9696},{"type":420,"value":755},{"type":415,"tag":443,"props":9750,"children":9752},{"id":9751},"an-example-with-visual-studio-2019-installation",[9753],{"type":420,"value":9754},"An example with Visual Studio 2019 installation",{"type":415,"tag":416,"props":9756,"children":9757},{},[9758],{"type":420,"value":9759},"If we take the example of Visual Studio 2019, let's say we want to install it with the English language pack and the Azure workload. To do that we can run the following command:",{"type":415,"tag":773,"props":9761,"children":9763},{"className":2236,"code":9762,"language":248,"meta":401,"style":401},"winget install Microsoft.VisualStudio.2019.Enterprise --silent --override \"--wait --quiet --addProductLang En-us --add Microsoft.VisualStudio.Workload.Azure\"\n",[9764],{"type":415,"tag":550,"props":9765,"children":9766},{"__ignoreMap":401},[9767],{"type":415,"tag":783,"props":9768,"children":9769},{"class":785,"line":786},[9770,9775,9780,9784,9788,9792,9796,9800,9804,9809],{"type":415,"tag":783,"props":9771,"children":9772},{"style":1362},[9773],{"type":420,"value":9774},"winget install Microsoft.VisualStudio.",{"type":415,"tag":783,"props":9776,"children":9777},{"style":902},[9778],{"type":420,"value":9779},"2019.",{"type":415,"tag":783,"props":9781,"children":9782},{"style":1362},[9783],{"type":420,"value":3597},{"type":415,"tag":783,"props":9785,"children":9786},{"style":790},[9787],{"type":420,"value":3366},{"type":415,"tag":783,"props":9789,"children":9790},{"style":1362},[9791],{"type":420,"value":3606},{"type":415,"tag":783,"props":9793,"children":9794},{"style":790},[9795],{"type":420,"value":3366},{"type":415,"tag":783,"props":9797,"children":9798},{"style":1362},[9799],{"type":420,"value":3615},{"type":415,"tag":783,"props":9801,"children":9802},{"style":790},[9803],{"type":420,"value":813},{"type":415,"tag":783,"props":9805,"children":9806},{"style":825},[9807],{"type":420,"value":9808},"--wait --quiet --addProductLang En-us --add Microsoft.VisualStudio.Workload.Azure",{"type":415,"tag":783,"props":9810,"children":9811},{"style":790},[9812],{"type":420,"value":3385},{"type":415,"tag":416,"props":9814,"children":9815},{},[9816,9818,9824],{"type":420,"value":9817},"You can find the documentation of the visual studio installer with the parameters I used ",{"type":415,"tag":423,"props":9819,"children":9822},{"href":9820,"rel":9821},"https://docs.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio?view=vs-2019#bootstrapper-commands-and-command-line-parameters",[427],[9823],{"type":420,"value":2266},{"type":420,"value":755},{"type":415,"tag":443,"props":9826,"children":9828},{"id":9827},"why-override",[9829,9831,9836],{"type":420,"value":9830},"Why ",{"type":415,"tag":550,"props":9832,"children":9834},{"className":9833},[],[9835],{"type":420,"value":9696},{"type":420,"value":9837},"?",{"type":415,"tag":416,"props":9839,"children":9840},{},[9841,9843,9848,9850,9855,9857,9864],{"type":420,"value":9842},"So ",{"type":415,"tag":550,"props":9844,"children":9846},{"className":9845},[],[9847],{"type":420,"value":9696},{"type":420,"value":9849}," is a really useful option because it allows us to customize our program installations. The name ",{"type":415,"tag":550,"props":9851,"children":9853},{"className":9852},[],[9854],{"type":420,"value":9696},{"type":420,"value":9856}," could seem a bit strange but it is to indicate that we override the default parameters that could have been specified in the manifest of the package you are installing. By the way, the packages and their manifests can be found in ",{"type":415,"tag":423,"props":9858,"children":9861},{"href":9859,"rel":9860},"https://github.com/microsoft/winget-pkgs/",[427],[9862],{"type":420,"value":9863},"this GitHub repository",{"type":420,"value":9865},". If you look at some of them you will see what parameters they use by default and be able to know if you need to override them to fit your needs.",{"type":415,"tag":443,"props":9867,"children":9869},{"id":9868},"pros-and-cons",[9870],{"type":420,"value":9871},"Pros and cons",{"type":415,"tag":416,"props":9873,"children":9874},{},[9875,9877,9882],{"type":420,"value":9876},"As we said, each installer has its own parameters so if we want to customize our installations we will have to look at the documentation of each installer which can be quite tiresome. If we compare to Chocolatey it is more or less the same but on Chocolatey site, each package has a page with most of the time the parameters documentation or at least a link to the documentation of the installer. Winget clearly lacks that. Yet, even if it requires a bit of effort to find the parameters of a package, thanks to the ",{"type":415,"tag":550,"props":9878,"children":9880},{"className":9879},[],[9881],{"type":420,"value":9696},{"type":420,"value":9883}," option we can fully customize its installation and that's great.",{"type":415,"tag":1420,"props":9885,"children":9886},{},[9887],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":9889},[9890,9891,9892,9894],{"id":9701,"depth":796,"text":9704},{"id":9751,"depth":796,"text":9754},{"id":9827,"depth":796,"text":9893},"Why override?",{"id":9868,"depth":796,"text":9871},"content:1.posts:18.winget-override.md","1.posts/18.winget-override.md",{"_path":58,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":57,"description":9898,"lead":9899,"date":9900,"image":9901,"badge":9902,"tags":9903,"body":9904,"_type":1435,"_id":10980,"_source":1437,"_file":10981,"_extension":1439},"Using Windows Package Manager import to install multiple applications.","Winget import a promising feature","2021-05-26T00:00:00.000Z",{"src":8344},{"label":408},[291,293,296,206],{"type":412,"children":9905,"toc":10974},[9906,9912,9931,9936,9950,9969,9975,10002,10019,10028,10034,10061,10066,10921,10933,10949,10954,10958,10970],{"type":415,"tag":443,"props":9907,"children":9909},{"id":9908},"about-windows-package-manager",[9910],{"type":420,"value":9911},"About Windows Package Manager",{"type":415,"tag":416,"props":9913,"children":9914},{},[9915,9917,9922,9924,9929],{"type":420,"value":9916},"You probably have already heard of the new ",{"type":415,"tag":423,"props":9918,"children":9920},{"href":3482,"rel":9919},[427],[9921],{"type":420,"value":2231},{"type":420,"value":9923}," and its command-line tool ",{"type":415,"tag":550,"props":9925,"children":9927},{"className":9926},[],[9928],{"type":420,"value":293},{"type":420,"value":9930}," that allows you to automate installing and upgrading software on your Windows 10 computer.",{"type":415,"tag":416,"props":9932,"children":9933},{},[9934],{"type":420,"value":9935},"With winget you can install an application very easily simply by executing in your terminal a command like this one which installs PowerToys:",{"type":415,"tag":773,"props":9937,"children":9939},{"className":2236,"code":9938,"language":248,"meta":401,"style":401},"winget install powertoys\n",[9940],{"type":415,"tag":550,"props":9941,"children":9942},{"__ignoreMap":401},[9943],{"type":415,"tag":783,"props":9944,"children":9945},{"class":785,"line":786},[9946],{"type":415,"tag":783,"props":9947,"children":9948},{"style":1362},[9949],{"type":420,"value":9938},{"type":415,"tag":416,"props":9951,"children":9952},{},[9953,9955,9960,9962,9967],{"type":420,"value":9954},"Currently, Windows Package Manager only offers basic features and has far fewer packages compared to other package managers like ",{"type":415,"tag":423,"props":9956,"children":9958},{"href":3490,"rel":9957},[427],[9959],{"type":420,"value":3494},{"type":420,"value":9961},". However, even if ",{"type":415,"tag":550,"props":9963,"children":9965},{"className":9964},[],[9966],{"type":420,"value":293},{"type":420,"value":9968}," is still in its early days, there are some promising features that make it a tool to consider when setting up a Windows 10 machine.",{"type":415,"tag":443,"props":9970,"children":9972},{"id":9971},"installing-microsoft-store-applications",[9973],{"type":420,"value":9974},"Installing Microsoft Store applications",{"type":415,"tag":416,"props":9976,"children":9977},{},[9978,9980,9985,9987,9992,9994,10000],{"type":420,"value":9979},"First, winget can install Microsoft Store applications. Most of the software you use as a developer probably does not come from Microsoft Store, but there are still some applications that it is handy to get from it. For instance, the new Windows Terminal is available as a Microsoft Store application. As far as I know, apart from ",{"type":415,"tag":550,"props":9981,"children":9983},{"className":9982},[],[9984],{"type":420,"value":293},{"type":420,"value":9986},", there is no other easy way to install a Microsoft Store application from the command line. With ",{"type":415,"tag":550,"props":9988,"children":9990},{"className":9989},[],[9991],{"type":420,"value":293},{"type":420,"value":9993}," you can just do: ",{"type":415,"tag":550,"props":9995,"children":9997},{"className":9996},[],[9998],{"type":420,"value":9999},"winget install Microsoft.WindowsTerminal -s msstore",{"type":420,"value":10001}," to install the new WindowsTerminal application from Microsoft Store.",{"type":415,"tag":588,"props":10003,"children":10005},{"icon":10004},"i-fluent-emoji-flat-pushpin",[10006],{"type":415,"tag":416,"props":10007,"children":10008},{},[10009,10011,10017],{"type":420,"value":10010},"Please note that at the time of writing, installing store applications from winget is an experimental feature that you have to enable in ",{"type":415,"tag":550,"props":10012,"children":10014},{"className":10013},[],[10015],{"type":420,"value":10016},"winget settings",{"type":420,"value":10018}," and that only a subset of Microsoft Store applications can be installed.",{"type":415,"tag":416,"props":10020,"children":10021},{},[10022],{"type":415,"tag":1322,"props":10023,"children":10027},{"alt":10024,"className":10025,"src":10026},"Winget configuration file.",[1326,1327],"/posts/images/winget_import_1.png",[],{"type":415,"tag":443,"props":10029,"children":10031},{"id":10030},"installing-several-packages-with-the-import-command",[10032],{"type":420,"value":10033},"Installing several packages with the import command",{"type":415,"tag":416,"props":10035,"children":10036},{},[10037,10039,10045,10047,10053,10055],{"type":420,"value":10038},"Second, winget has an ",{"type":415,"tag":550,"props":10040,"children":10042},{"className":10041},[],[10043],{"type":420,"value":10044},"import",{"type":420,"value":10046}," command that allows you installing all the package specified in a JSON file you pass in parameter. It means that instead of writing a script with many install commands for each of the packages you want to install, you can write a ",{"type":415,"tag":550,"props":10048,"children":10050},{"className":10049},[],[10051],{"type":420,"value":10052},"package.json",{"type":420,"value":10054}," file that will contain all the packages you want to install, their version, the source of the package (place to find them, msstore for Microsoft Store applications), ... and you will be able to install the software with one command: ",{"type":415,"tag":550,"props":10056,"children":10058},{"className":10057},[],[10059],{"type":420,"value":10060},"winget import packages.json",{"type":415,"tag":416,"props":10062,"children":10063},{},[10064],{"type":420,"value":10065},"Here is an example of such a file:",{"type":415,"tag":773,"props":10067,"children":10069},{"className":775,"code":10068,"language":777,"meta":401,"style":401},"{\n    \"$schema\" : \"https://aka.ms/winget-packages.schema.2.0.json\",\n    \"CreationDate\" : \"2021-05-23T14:41:38.200-00:00\",\n    \"Sources\" : \n    [\n        {\n            \"Packages\" : \n            [\n                {\n                    \"PackageIdentifier\" : \"Microsoft.Whiteboard\"\n                },\n                {\n                    \"PackageIdentifier\" : \"Microsoft.WindowsTerminal\"   \n                }\n            ],\n            \"SourceDetails\" : \n            {\n                \"Argument\" : \"https://winget.azureedge.net/msstore\",\n                \"Identifier\" : \"Microsoft.Winget.MSStore.Source_8wekyb3d8bbwe\",\n                \"Name\" : \"msstore\",\n                \"Type\" : \"Microsoft.PreIndexed.Package\"\n            }\n        },\n        {\n            \"Packages\" : \n            [\n                {\n                    \"PackageIdentifier\": \"Microsoft.AzureCLI\"\n                },\n                {\n                    \"PackageIdentifier\" : \"Microsoft.PowerToys\"\n                }\n            ],\n            \"SourceDetails\" : \n            {\n                \"Argument\" : \"https://winget.azureedge.net/cache\",\n                \"Identifier\" : \"Microsoft.Winget.Source_8wekyb3d8bbwe\",\n                \"Name\" : \"winget\",\n                \"Type\" : \"Microsoft.PreIndexed.Package\"\n            }\n        }\n    ],\n    \"WinGetVersion\" : \"0.4.11391\"\n}\n",[10070],{"type":415,"tag":550,"props":10071,"children":10072},{"__ignoreMap":401},[10073,10080,10118,10155,10179,10187,10195,10220,10228,10236,10270,10278,10285,10322,10330,10338,10362,10370,10408,10445,10482,10515,10524,10533,10541,10565,10573,10581,10614,10622,10630,10663,10671,10679,10703,10711,10748,10785,10821,10853,10861,10870,10879,10913],{"type":415,"tag":783,"props":10074,"children":10075},{"class":785,"line":786},[10076],{"type":415,"tag":783,"props":10077,"children":10078},{"style":790},[10079],{"type":420,"value":793},{"type":415,"tag":783,"props":10081,"children":10082},{"class":785,"line":796},[10083,10087,10092,10096,10101,10105,10110,10114],{"type":415,"tag":783,"props":10084,"children":10085},{"style":790},[10086],{"type":420,"value":872},{"type":415,"tag":783,"props":10088,"children":10089},{"style":805},[10090],{"type":420,"value":10091},"$schema",{"type":415,"tag":783,"props":10093,"children":10094},{"style":790},[10095],{"type":420,"value":813},{"type":415,"tag":783,"props":10097,"children":10098},{"style":790},[10099],{"type":420,"value":10100}," :",{"type":415,"tag":783,"props":10102,"children":10103},{"style":790},[10104],{"type":420,"value":822},{"type":415,"tag":783,"props":10106,"children":10107},{"style":825},[10108],{"type":420,"value":10109},"https://aka.ms/winget-packages.schema.2.0.json",{"type":415,"tag":783,"props":10111,"children":10112},{"style":790},[10113],{"type":420,"value":813},{"type":415,"tag":783,"props":10115,"children":10116},{"style":790},[10117],{"type":420,"value":837},{"type":415,"tag":783,"props":10119,"children":10120},{"class":785,"line":840},[10121,10125,10130,10134,10138,10142,10147,10151],{"type":415,"tag":783,"props":10122,"children":10123},{"style":790},[10124],{"type":420,"value":872},{"type":415,"tag":783,"props":10126,"children":10127},{"style":805},[10128],{"type":420,"value":10129},"CreationDate",{"type":415,"tag":783,"props":10131,"children":10132},{"style":790},[10133],{"type":420,"value":813},{"type":415,"tag":783,"props":10135,"children":10136},{"style":790},[10137],{"type":420,"value":10100},{"type":415,"tag":783,"props":10139,"children":10140},{"style":790},[10141],{"type":420,"value":822},{"type":415,"tag":783,"props":10143,"children":10144},{"style":825},[10145],{"type":420,"value":10146},"2021-05-23T14:41:38.200-00:00",{"type":415,"tag":783,"props":10148,"children":10149},{"style":790},[10150],{"type":420,"value":813},{"type":415,"tag":783,"props":10152,"children":10153},{"style":790},[10154],{"type":420,"value":837},{"type":415,"tag":783,"props":10156,"children":10157},{"class":785,"line":866},[10158,10162,10167,10171,10175],{"type":415,"tag":783,"props":10159,"children":10160},{"style":790},[10161],{"type":420,"value":872},{"type":415,"tag":783,"props":10163,"children":10164},{"style":805},[10165],{"type":420,"value":10166},"Sources",{"type":415,"tag":783,"props":10168,"children":10169},{"style":790},[10170],{"type":420,"value":813},{"type":415,"tag":783,"props":10172,"children":10173},{"style":790},[10174],{"type":420,"value":10100},{"type":415,"tag":783,"props":10176,"children":10177},{"style":1362},[10178],{"type":420,"value":8752},{"type":415,"tag":783,"props":10180,"children":10181},{"class":785,"line":893},[10182],{"type":415,"tag":783,"props":10183,"children":10184},{"style":790},[10185],{"type":420,"value":10186},"    [\n",{"type":415,"tag":783,"props":10188,"children":10189},{"class":785,"line":920},[10190],{"type":415,"tag":783,"props":10191,"children":10192},{"style":790},[10193],{"type":420,"value":10194},"        {\n",{"type":415,"tag":783,"props":10196,"children":10197},{"class":785,"line":959},[10198,10203,10208,10212,10216],{"type":415,"tag":783,"props":10199,"children":10200},{"style":790},[10201],{"type":420,"value":10202},"            \"",{"type":415,"tag":783,"props":10204,"children":10205},{"style":875},[10206],{"type":420,"value":10207},"Packages",{"type":415,"tag":783,"props":10209,"children":10210},{"style":790},[10211],{"type":420,"value":813},{"type":415,"tag":783,"props":10213,"children":10214},{"style":790},[10215],{"type":420,"value":10100},{"type":415,"tag":783,"props":10217,"children":10218},{"style":1362},[10219],{"type":420,"value":8752},{"type":415,"tag":783,"props":10221,"children":10222},{"class":785,"line":997},[10223],{"type":415,"tag":783,"props":10224,"children":10225},{"style":790},[10226],{"type":420,"value":10227},"            [\n",{"type":415,"tag":783,"props":10229,"children":10230},{"class":785,"line":1023},[10231],{"type":415,"tag":783,"props":10232,"children":10233},{"style":790},[10234],{"type":420,"value":10235},"                {\n",{"type":415,"tag":783,"props":10237,"children":10238},{"class":785,"line":1061},[10239,10244,10249,10253,10257,10261,10266],{"type":415,"tag":783,"props":10240,"children":10241},{"style":790},[10242],{"type":420,"value":10243},"                    \"",{"type":415,"tag":783,"props":10245,"children":10246},{"style":902},[10247],{"type":420,"value":10248},"PackageIdentifier",{"type":415,"tag":783,"props":10250,"children":10251},{"style":790},[10252],{"type":420,"value":813},{"type":415,"tag":783,"props":10254,"children":10255},{"style":790},[10256],{"type":420,"value":10100},{"type":415,"tag":783,"props":10258,"children":10259},{"style":790},[10260],{"type":420,"value":822},{"type":415,"tag":783,"props":10262,"children":10263},{"style":825},[10264],{"type":420,"value":10265},"Microsoft.Whiteboard",{"type":415,"tag":783,"props":10267,"children":10268},{"style":790},[10269],{"type":420,"value":3385},{"type":415,"tag":783,"props":10271,"children":10272},{"class":785,"line":1099},[10273],{"type":415,"tag":783,"props":10274,"children":10275},{"style":790},[10276],{"type":420,"value":10277},"                },\n",{"type":415,"tag":783,"props":10279,"children":10280},{"class":785,"line":1137},[10281],{"type":415,"tag":783,"props":10282,"children":10283},{"style":790},[10284],{"type":420,"value":10235},{"type":415,"tag":783,"props":10286,"children":10287},{"class":785,"line":1175},[10288,10292,10296,10300,10304,10308,10313,10317],{"type":415,"tag":783,"props":10289,"children":10290},{"style":790},[10291],{"type":420,"value":10243},{"type":415,"tag":783,"props":10293,"children":10294},{"style":902},[10295],{"type":420,"value":10248},{"type":415,"tag":783,"props":10297,"children":10298},{"style":790},[10299],{"type":420,"value":813},{"type":415,"tag":783,"props":10301,"children":10302},{"style":790},[10303],{"type":420,"value":10100},{"type":415,"tag":783,"props":10305,"children":10306},{"style":790},[10307],{"type":420,"value":822},{"type":415,"tag":783,"props":10309,"children":10310},{"style":825},[10311],{"type":420,"value":10312},"Microsoft.WindowsTerminal",{"type":415,"tag":783,"props":10314,"children":10315},{"style":790},[10316],{"type":420,"value":813},{"type":415,"tag":783,"props":10318,"children":10319},{"style":1362},[10320],{"type":420,"value":10321},"   \n",{"type":415,"tag":783,"props":10323,"children":10324},{"class":785,"line":1213},[10325],{"type":415,"tag":783,"props":10326,"children":10327},{"style":790},[10328],{"type":420,"value":10329},"                }\n",{"type":415,"tag":783,"props":10331,"children":10332},{"class":785,"line":1239},[10333],{"type":415,"tag":783,"props":10334,"children":10335},{"style":790},[10336],{"type":420,"value":10337},"            ],\n",{"type":415,"tag":783,"props":10339,"children":10340},{"class":785,"line":1248},[10341,10345,10350,10354,10358],{"type":415,"tag":783,"props":10342,"children":10343},{"style":790},[10344],{"type":420,"value":10202},{"type":415,"tag":783,"props":10346,"children":10347},{"style":875},[10348],{"type":420,"value":10349},"SourceDetails",{"type":415,"tag":783,"props":10351,"children":10352},{"style":790},[10353],{"type":420,"value":813},{"type":415,"tag":783,"props":10355,"children":10356},{"style":790},[10357],{"type":420,"value":10100},{"type":415,"tag":783,"props":10359,"children":10360},{"style":1362},[10361],{"type":420,"value":8752},{"type":415,"tag":783,"props":10363,"children":10364},{"class":785,"line":1257},[10365],{"type":415,"tag":783,"props":10366,"children":10367},{"style":790},[10368],{"type":420,"value":10369},"            {\n",{"type":415,"tag":783,"props":10371,"children":10372},{"class":785,"line":1266},[10373,10378,10383,10387,10391,10395,10400,10404],{"type":415,"tag":783,"props":10374,"children":10375},{"style":790},[10376],{"type":420,"value":10377},"                \"",{"type":415,"tag":783,"props":10379,"children":10380},{"style":902},[10381],{"type":420,"value":10382},"Argument",{"type":415,"tag":783,"props":10384,"children":10385},{"style":790},[10386],{"type":420,"value":813},{"type":415,"tag":783,"props":10388,"children":10389},{"style":790},[10390],{"type":420,"value":10100},{"type":415,"tag":783,"props":10392,"children":10393},{"style":790},[10394],{"type":420,"value":822},{"type":415,"tag":783,"props":10396,"children":10397},{"style":825},[10398],{"type":420,"value":10399},"https://winget.azureedge.net/msstore",{"type":415,"tag":783,"props":10401,"children":10402},{"style":790},[10403],{"type":420,"value":813},{"type":415,"tag":783,"props":10405,"children":10406},{"style":790},[10407],{"type":420,"value":837},{"type":415,"tag":783,"props":10409,"children":10410},{"class":785,"line":6616},[10411,10415,10420,10424,10428,10432,10437,10441],{"type":415,"tag":783,"props":10412,"children":10413},{"style":790},[10414],{"type":420,"value":10377},{"type":415,"tag":783,"props":10416,"children":10417},{"style":902},[10418],{"type":420,"value":10419},"Identifier",{"type":415,"tag":783,"props":10421,"children":10422},{"style":790},[10423],{"type":420,"value":813},{"type":415,"tag":783,"props":10425,"children":10426},{"style":790},[10427],{"type":420,"value":10100},{"type":415,"tag":783,"props":10429,"children":10430},{"style":790},[10431],{"type":420,"value":822},{"type":415,"tag":783,"props":10433,"children":10434},{"style":825},[10435],{"type":420,"value":10436},"Microsoft.Winget.MSStore.Source_8wekyb3d8bbwe",{"type":415,"tag":783,"props":10438,"children":10439},{"style":790},[10440],{"type":420,"value":813},{"type":415,"tag":783,"props":10442,"children":10443},{"style":790},[10444],{"type":420,"value":837},{"type":415,"tag":783,"props":10446,"children":10447},{"class":785,"line":6648},[10448,10452,10457,10461,10465,10469,10474,10478],{"type":415,"tag":783,"props":10449,"children":10450},{"style":790},[10451],{"type":420,"value":10377},{"type":415,"tag":783,"props":10453,"children":10454},{"style":902},[10455],{"type":420,"value":10456},"Name",{"type":415,"tag":783,"props":10458,"children":10459},{"style":790},[10460],{"type":420,"value":813},{"type":415,"tag":783,"props":10462,"children":10463},{"style":790},[10464],{"type":420,"value":10100},{"type":415,"tag":783,"props":10466,"children":10467},{"style":790},[10468],{"type":420,"value":822},{"type":415,"tag":783,"props":10470,"children":10471},{"style":825},[10472],{"type":420,"value":10473},"msstore",{"type":415,"tag":783,"props":10475,"children":10476},{"style":790},[10477],{"type":420,"value":813},{"type":415,"tag":783,"props":10479,"children":10480},{"style":790},[10481],{"type":420,"value":837},{"type":415,"tag":783,"props":10483,"children":10485},{"class":785,"line":10484},21,[10486,10490,10494,10498,10502,10506,10511],{"type":415,"tag":783,"props":10487,"children":10488},{"style":790},[10489],{"type":420,"value":10377},{"type":415,"tag":783,"props":10491,"children":10492},{"style":902},[10493],{"type":420,"value":932},{"type":415,"tag":783,"props":10495,"children":10496},{"style":790},[10497],{"type":420,"value":813},{"type":415,"tag":783,"props":10499,"children":10500},{"style":790},[10501],{"type":420,"value":10100},{"type":415,"tag":783,"props":10503,"children":10504},{"style":790},[10505],{"type":420,"value":822},{"type":415,"tag":783,"props":10507,"children":10508},{"style":825},[10509],{"type":420,"value":10510},"Microsoft.PreIndexed.Package",{"type":415,"tag":783,"props":10512,"children":10513},{"style":790},[10514],{"type":420,"value":3385},{"type":415,"tag":783,"props":10516,"children":10518},{"class":785,"line":10517},22,[10519],{"type":415,"tag":783,"props":10520,"children":10521},{"style":790},[10522],{"type":420,"value":10523},"            }\n",{"type":415,"tag":783,"props":10525,"children":10527},{"class":785,"line":10526},23,[10528],{"type":415,"tag":783,"props":10529,"children":10530},{"style":790},[10531],{"type":420,"value":10532},"        },\n",{"type":415,"tag":783,"props":10534,"children":10536},{"class":785,"line":10535},24,[10537],{"type":415,"tag":783,"props":10538,"children":10539},{"style":790},[10540],{"type":420,"value":10194},{"type":415,"tag":783,"props":10542,"children":10544},{"class":785,"line":10543},25,[10545,10549,10553,10557,10561],{"type":415,"tag":783,"props":10546,"children":10547},{"style":790},[10548],{"type":420,"value":10202},{"type":415,"tag":783,"props":10550,"children":10551},{"style":875},[10552],{"type":420,"value":10207},{"type":415,"tag":783,"props":10554,"children":10555},{"style":790},[10556],{"type":420,"value":813},{"type":415,"tag":783,"props":10558,"children":10559},{"style":790},[10560],{"type":420,"value":10100},{"type":415,"tag":783,"props":10562,"children":10563},{"style":1362},[10564],{"type":420,"value":8752},{"type":415,"tag":783,"props":10566,"children":10568},{"class":785,"line":10567},26,[10569],{"type":415,"tag":783,"props":10570,"children":10571},{"style":790},[10572],{"type":420,"value":10227},{"type":415,"tag":783,"props":10574,"children":10576},{"class":785,"line":10575},27,[10577],{"type":415,"tag":783,"props":10578,"children":10579},{"style":790},[10580],{"type":420,"value":10235},{"type":415,"tag":783,"props":10582,"children":10584},{"class":785,"line":10583},28,[10585,10589,10593,10597,10601,10605,10610],{"type":415,"tag":783,"props":10586,"children":10587},{"style":790},[10588],{"type":420,"value":10243},{"type":415,"tag":783,"props":10590,"children":10591},{"style":902},[10592],{"type":420,"value":10248},{"type":415,"tag":783,"props":10594,"children":10595},{"style":790},[10596],{"type":420,"value":813},{"type":415,"tag":783,"props":10598,"children":10599},{"style":790},[10600],{"type":420,"value":635},{"type":415,"tag":783,"props":10602,"children":10603},{"style":790},[10604],{"type":420,"value":822},{"type":415,"tag":783,"props":10606,"children":10607},{"style":825},[10608],{"type":420,"value":10609},"Microsoft.AzureCLI",{"type":415,"tag":783,"props":10611,"children":10612},{"style":790},[10613],{"type":420,"value":3385},{"type":415,"tag":783,"props":10615,"children":10617},{"class":785,"line":10616},29,[10618],{"type":415,"tag":783,"props":10619,"children":10620},{"style":790},[10621],{"type":420,"value":10277},{"type":415,"tag":783,"props":10623,"children":10625},{"class":785,"line":10624},30,[10626],{"type":415,"tag":783,"props":10627,"children":10628},{"style":790},[10629],{"type":420,"value":10235},{"type":415,"tag":783,"props":10631,"children":10633},{"class":785,"line":10632},31,[10634,10638,10642,10646,10650,10654,10659],{"type":415,"tag":783,"props":10635,"children":10636},{"style":790},[10637],{"type":420,"value":10243},{"type":415,"tag":783,"props":10639,"children":10640},{"style":902},[10641],{"type":420,"value":10248},{"type":415,"tag":783,"props":10643,"children":10644},{"style":790},[10645],{"type":420,"value":813},{"type":415,"tag":783,"props":10647,"children":10648},{"style":790},[10649],{"type":420,"value":10100},{"type":415,"tag":783,"props":10651,"children":10652},{"style":790},[10653],{"type":420,"value":822},{"type":415,"tag":783,"props":10655,"children":10656},{"style":825},[10657],{"type":420,"value":10658},"Microsoft.PowerToys",{"type":415,"tag":783,"props":10660,"children":10661},{"style":790},[10662],{"type":420,"value":3385},{"type":415,"tag":783,"props":10664,"children":10666},{"class":785,"line":10665},32,[10667],{"type":415,"tag":783,"props":10668,"children":10669},{"style":790},[10670],{"type":420,"value":10329},{"type":415,"tag":783,"props":10672,"children":10674},{"class":785,"line":10673},33,[10675],{"type":415,"tag":783,"props":10676,"children":10677},{"style":790},[10678],{"type":420,"value":10337},{"type":415,"tag":783,"props":10680,"children":10682},{"class":785,"line":10681},34,[10683,10687,10691,10695,10699],{"type":415,"tag":783,"props":10684,"children":10685},{"style":790},[10686],{"type":420,"value":10202},{"type":415,"tag":783,"props":10688,"children":10689},{"style":875},[10690],{"type":420,"value":10349},{"type":415,"tag":783,"props":10692,"children":10693},{"style":790},[10694],{"type":420,"value":813},{"type":415,"tag":783,"props":10696,"children":10697},{"style":790},[10698],{"type":420,"value":10100},{"type":415,"tag":783,"props":10700,"children":10701},{"style":1362},[10702],{"type":420,"value":8752},{"type":415,"tag":783,"props":10704,"children":10706},{"class":785,"line":10705},35,[10707],{"type":415,"tag":783,"props":10708,"children":10709},{"style":790},[10710],{"type":420,"value":10369},{"type":415,"tag":783,"props":10712,"children":10714},{"class":785,"line":10713},36,[10715,10719,10723,10727,10731,10735,10740,10744],{"type":415,"tag":783,"props":10716,"children":10717},{"style":790},[10718],{"type":420,"value":10377},{"type":415,"tag":783,"props":10720,"children":10721},{"style":902},[10722],{"type":420,"value":10382},{"type":415,"tag":783,"props":10724,"children":10725},{"style":790},[10726],{"type":420,"value":813},{"type":415,"tag":783,"props":10728,"children":10729},{"style":790},[10730],{"type":420,"value":10100},{"type":415,"tag":783,"props":10732,"children":10733},{"style":790},[10734],{"type":420,"value":822},{"type":415,"tag":783,"props":10736,"children":10737},{"style":825},[10738],{"type":420,"value":10739},"https://winget.azureedge.net/cache",{"type":415,"tag":783,"props":10741,"children":10742},{"style":790},[10743],{"type":420,"value":813},{"type":415,"tag":783,"props":10745,"children":10746},{"style":790},[10747],{"type":420,"value":837},{"type":415,"tag":783,"props":10749,"children":10751},{"class":785,"line":10750},37,[10752,10756,10760,10764,10768,10772,10777,10781],{"type":415,"tag":783,"props":10753,"children":10754},{"style":790},[10755],{"type":420,"value":10377},{"type":415,"tag":783,"props":10757,"children":10758},{"style":902},[10759],{"type":420,"value":10419},{"type":415,"tag":783,"props":10761,"children":10762},{"style":790},[10763],{"type":420,"value":813},{"type":415,"tag":783,"props":10765,"children":10766},{"style":790},[10767],{"type":420,"value":10100},{"type":415,"tag":783,"props":10769,"children":10770},{"style":790},[10771],{"type":420,"value":822},{"type":415,"tag":783,"props":10773,"children":10774},{"style":825},[10775],{"type":420,"value":10776},"Microsoft.Winget.Source_8wekyb3d8bbwe",{"type":415,"tag":783,"props":10778,"children":10779},{"style":790},[10780],{"type":420,"value":813},{"type":415,"tag":783,"props":10782,"children":10783},{"style":790},[10784],{"type":420,"value":837},{"type":415,"tag":783,"props":10786,"children":10788},{"class":785,"line":10787},38,[10789,10793,10797,10801,10805,10809,10813,10817],{"type":415,"tag":783,"props":10790,"children":10791},{"style":790},[10792],{"type":420,"value":10377},{"type":415,"tag":783,"props":10794,"children":10795},{"style":902},[10796],{"type":420,"value":10456},{"type":415,"tag":783,"props":10798,"children":10799},{"style":790},[10800],{"type":420,"value":813},{"type":415,"tag":783,"props":10802,"children":10803},{"style":790},[10804],{"type":420,"value":10100},{"type":415,"tag":783,"props":10806,"children":10807},{"style":790},[10808],{"type":420,"value":822},{"type":415,"tag":783,"props":10810,"children":10811},{"style":825},[10812],{"type":420,"value":293},{"type":415,"tag":783,"props":10814,"children":10815},{"style":790},[10816],{"type":420,"value":813},{"type":415,"tag":783,"props":10818,"children":10819},{"style":790},[10820],{"type":420,"value":837},{"type":415,"tag":783,"props":10822,"children":10824},{"class":785,"line":10823},39,[10825,10829,10833,10837,10841,10845,10849],{"type":415,"tag":783,"props":10826,"children":10827},{"style":790},[10828],{"type":420,"value":10377},{"type":415,"tag":783,"props":10830,"children":10831},{"style":902},[10832],{"type":420,"value":932},{"type":415,"tag":783,"props":10834,"children":10835},{"style":790},[10836],{"type":420,"value":813},{"type":415,"tag":783,"props":10838,"children":10839},{"style":790},[10840],{"type":420,"value":10100},{"type":415,"tag":783,"props":10842,"children":10843},{"style":790},[10844],{"type":420,"value":822},{"type":415,"tag":783,"props":10846,"children":10847},{"style":825},[10848],{"type":420,"value":10510},{"type":415,"tag":783,"props":10850,"children":10851},{"style":790},[10852],{"type":420,"value":3385},{"type":415,"tag":783,"props":10854,"children":10856},{"class":785,"line":10855},40,[10857],{"type":415,"tag":783,"props":10858,"children":10859},{"style":790},[10860],{"type":420,"value":10523},{"type":415,"tag":783,"props":10862,"children":10864},{"class":785,"line":10863},41,[10865],{"type":415,"tag":783,"props":10866,"children":10867},{"style":790},[10868],{"type":420,"value":10869},"        }\n",{"type":415,"tag":783,"props":10871,"children":10873},{"class":785,"line":10872},42,[10874],{"type":415,"tag":783,"props":10875,"children":10876},{"style":790},[10877],{"type":420,"value":10878},"    ],\n",{"type":415,"tag":783,"props":10880,"children":10882},{"class":785,"line":10881},43,[10883,10887,10892,10896,10900,10904,10909],{"type":415,"tag":783,"props":10884,"children":10885},{"style":790},[10886],{"type":420,"value":872},{"type":415,"tag":783,"props":10888,"children":10889},{"style":805},[10890],{"type":420,"value":10891},"WinGetVersion",{"type":415,"tag":783,"props":10893,"children":10894},{"style":790},[10895],{"type":420,"value":813},{"type":415,"tag":783,"props":10897,"children":10898},{"style":790},[10899],{"type":420,"value":10100},{"type":415,"tag":783,"props":10901,"children":10902},{"style":790},[10903],{"type":420,"value":822},{"type":415,"tag":783,"props":10905,"children":10906},{"style":825},[10907],{"type":420,"value":10908},"0.4.11391",{"type":415,"tag":783,"props":10910,"children":10911},{"style":790},[10912],{"type":420,"value":3385},{"type":415,"tag":783,"props":10914,"children":10916},{"class":785,"line":10915},44,[10917],{"type":415,"tag":783,"props":10918,"children":10919},{"style":790},[10920],{"type":420,"value":1272},{"type":415,"tag":416,"props":10922,"children":10923},{},[10924,10926,10931],{"type":420,"value":10925},"It contains packages from Microsoft Store and packages from the ",{"type":415,"tag":550,"props":10927,"children":10929},{"className":10928},[],[10930],{"type":420,"value":293},{"type":420,"value":10932}," package repository hence the 2 arrays of packages (one by each source).",{"type":415,"tag":588,"props":10934,"children":10935},{"icon":10004},[10936],{"type":415,"tag":416,"props":10937,"children":10938},{},[10939,10941,10947],{"type":420,"value":10940},"You can easily edit this JSON in vscode with autocompletion thanks to the link to the JSON ",{"type":415,"tag":550,"props":10942,"children":10944},{"className":10943},[],[10945],{"type":420,"value":10946},"schema",{"type":420,"value":10948}," at the beginning of the JSON.",{"type":415,"tag":416,"props":10950,"children":10951},{},[10952],{"type":420,"value":10953},"Import is great but there are still things missing like the ability to silently install the applications which is possible with the install command.",{"type":415,"tag":443,"props":10955,"children":10956},{"id":2942},[10957],{"type":420,"value":2945},{"type":415,"tag":416,"props":10959,"children":10960},{},[10961,10963,10968],{"type":420,"value":10962},"Chocolatey will continue to be my main package manager for now: on the one hand for the number of packages available and on the other hand for being able to specify some parameters for a package installation (like the workload and components to install for Visual Studio 2019). Yet, ",{"type":415,"tag":550,"props":10964,"children":10966},{"className":10965},[],[10967],{"type":420,"value":293},{"type":420,"value":10969}," will be part of my toolbox as well to install some packages (including Microsoft Store applications) and I expect it to continue to get better and better.",{"type":415,"tag":1420,"props":10971,"children":10972},{},[10973],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":10975},[10976,10977,10978,10979],{"id":9908,"depth":796,"text":9911},{"id":9971,"depth":796,"text":9974},{"id":10030,"depth":796,"text":10033},{"id":2942,"depth":796,"text":2945},"content:1.posts:17.winget-import.md","1.posts/17.winget-import.md",{"_path":46,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":45,"description":10983,"lead":10984,"date":10985,"image":10986,"badge":10987,"tags":10988,"body":10989,"_type":1435,"_id":11192,"_source":1437,"_file":11193,"_extension":1439},"I often see developers talking on Twitter or Dev.to about things they have learned during the previous day or the previous week. I like the idea so I decided to write my first article about tips I learned during this past week. I am not intending to write an article like this every week but from time to time when I feel I have something interesting to share or that I want to keep track of for myself.","Windows Terminal startup actions, a git config setting for submodules, and a better IntelliSense for azure pipelines vscode extension.","2021-03-07T00:00:00.000Z",{"src":2208},{"label":2210},[272,241,206,275,278],{"type":412,"children":10990,"toc":11187},[10991,10995,11001,11006,11011,11052,11061,11067,11072,11092,11120,11132,11138,11152,11157,11166,11179,11183],{"type":415,"tag":416,"props":10992,"children":10993},{},[10994],{"type":420,"value":10983},{"type":415,"tag":443,"props":10996,"children":10998},{"id":10997},"start-windows-terminal-with-multiple-panes-thanks-to-startup-actions",[10999],{"type":420,"value":11000},"Start Windows Terminal with multiple panes thanks to startup actions.",{"type":415,"tag":416,"props":11002,"children":11003},{},[11004],{"type":420,"value":11005},"The new Windows Terminal has evolved a lot since its first release in preview in 2019. It now has a lot of nice features and it keeps getting better which is awesome. I was previously using Cmder but Windows Terminal has quickly become my default terminal.",{"type":415,"tag":416,"props":11007,"children":11008},{},[11009],{"type":420,"value":11010},"One of the latest releases of Windows Terminal allows specifying startup actions in your settings which is great if you want your terminal to open multiple panes or tabs with some specific profiles when the terminal starts. You can find below an example where I tell the terminal to open 3 panes vertically on 3 different locations using my PowerShell profile for each one. It is especially interesting when your daily work is about working on different git repositories.",{"type":415,"tag":773,"props":11012,"children":11014},{"className":775,"code":11013,"language":777,"meta":401,"style":401},"\"startupActions\": \"new-tab -p PowerShell -d d:/dev/MyApi1; split-pane -p PowerShell -V -d d:/dev/MyApi2; split-pane -p PowerShell -V -d d:/dev/MyAzureFunctions\"\n",[11015],{"type":415,"tag":550,"props":11016,"children":11017},{"__ignoreMap":401},[11018],{"type":415,"tag":783,"props":11019,"children":11020},{"class":785,"line":786},[11021,11025,11030,11034,11039,11043,11048],{"type":415,"tag":783,"props":11022,"children":11023},{"style":790},[11024],{"type":420,"value":813},{"type":415,"tag":783,"props":11026,"children":11027},{"style":825},[11028],{"type":420,"value":11029},"startupActions",{"type":415,"tag":783,"props":11031,"children":11032},{"style":790},[11033],{"type":420,"value":813},{"type":415,"tag":783,"props":11035,"children":11036},{"style":1362},[11037],{"type":420,"value":11038},": ",{"type":415,"tag":783,"props":11040,"children":11041},{"style":790},[11042],{"type":420,"value":813},{"type":415,"tag":783,"props":11044,"children":11045},{"style":825},[11046],{"type":420,"value":11047},"new-tab -p PowerShell -d d:/dev/MyApi1; split-pane -p PowerShell -V -d d:/dev/MyApi2; split-pane -p PowerShell -V -d d:/dev/MyAzureFunctions",{"type":415,"tag":783,"props":11049,"children":11050},{"style":790},[11051],{"type":420,"value":3385},{"type":415,"tag":416,"props":11053,"children":11054},{},[11055],{"type":415,"tag":1322,"props":11056,"children":11060},{"alt":11057,"className":11058,"src":11059},"A Windows terminal split int 3 PowerShell tabs.",[1326,1327],"/posts/images/w092021tips_terminal_1.png",[],{"type":415,"tag":443,"props":11062,"children":11064},{"id":11063},"a-git-config-setting-to-make-working-with-submodule-easier",[11065],{"type":420,"value":11066},"A git config setting to make working with submodule easier.",{"type":415,"tag":416,"props":11068,"children":11069},{},[11070],{"type":420,"value":11071},"On the projects I am working on, we are using submodules to share some code between different components. NuGet packages are great to share code between different projects or applications but it is sometimes a bit complicated to handle when you simply want to share a few models and services between an API and an Azure Function for instance. In these situations, it is easier to use submodules for which you don't have to handle versioning (you just reference in your repository a commit or a branch of the submodule you want to use) nor set up source link (you can directly debug the code from the submodules in your project).",{"type":415,"tag":416,"props":11073,"children":11074},{},[11075,11077,11082,11084,11090],{"type":420,"value":11076},"However, one drawback of using submodules is that you have to learn and execute a few additional git commands to manipulate submodules. Typically when you do a ",{"type":415,"tag":550,"props":11078,"children":11080},{"className":11079},[],[11081],{"type":420,"value":2993},{"type":420,"value":11083}," in your git repository, you have to do a ",{"type":415,"tag":550,"props":11085,"children":11087},{"className":11086},[],[11088],{"type":420,"value":11089},"git submodule update",{"type":420,"value":11091}," to update the submodules to their respective commit referenced in the \"super\" git repository. That is just one additional command but if you do that often it can quickly become boring 🥱.",{"type":415,"tag":416,"props":11093,"children":11094},{},[11095,11097,11103,11105,11110,11112,11119],{"type":420,"value":11096},"I never really look for a way to make that easier until this week where I discovered that you could pass the ",{"type":415,"tag":550,"props":11098,"children":11100},{"className":11099},[],[11101],{"type":420,"value":11102},"--recurse-submodules",{"type":420,"value":11104}," flag to a ",{"type":415,"tag":550,"props":11106,"children":11108},{"className":11107},[],[11109],{"type":420,"value":2993},{"type":420,"value":11111}," command to automate the process. It works for other commands as well as solving other similar inconveniences. And the best is that it is available as a setting to put in your git configuration. For more information have a look at ",{"type":415,"tag":423,"props":11113,"children":11116},{"href":11114,"rel":11115},"https://git-scm.com/book/en/v2/Git-Tools-Submodules",[427],[11117],{"type":420,"value":11118},"git documentation on the topic",{"type":420,"value":755},{"type":415,"tag":416,"props":11121,"children":11122},{},[11123,11125,11131],{"type":420,"value":11124},"So just execute the following command and be more productive with submodules 🐱‍🏍:\n",{"type":415,"tag":550,"props":11126,"children":11128},{"className":11127},[],[11129],{"type":420,"value":11130},"git config submodule.recurse true",{"type":420,"value":755},{"type":415,"tag":443,"props":11133,"children":11135},{"id":11134},"intellisense-for-azure-pipelines-custom-tasks-in-vscode",[11136],{"type":420,"value":11137},"IntelliSense for Azure Pipelines custom tasks in vscode.",{"type":415,"tag":416,"props":11139,"children":11140},{},[11141,11143,11150],{"type":420,"value":11142},"Azure Pipelines is an awesome CI/CD tool to automate your builds and deployments. The only problem when writing pipelines is that you have to write YAML  😿. I guess we just have to get used to writing YAML because it has become something used everywhere. Fortunately, there is a ",{"type":415,"tag":423,"props":11144,"children":11147},{"href":11145,"rel":11146},"https://marketplace.visualstudio.com/items?itemName=ms-azure-devops.azure-pipelines",[427],[11148],{"type":420,"value":11149},"vscode extension",{"type":420,"value":11151}," that helps writing Azure Pipelines files by providing syntax highlighting and autocompletion for Azure Pipelines YAML files in vscode.",{"type":415,"tag":416,"props":11153,"children":11154},{},[11155],{"type":420,"value":11156},"The extension validates the YAML files by using a generic YAML schema containing the in-box tasks of Azure Pipelines yet that means it is not able to validate tasks that come from extensions you installed in Azure DevOps. But here comes the good news: you can provide your custom schema to the extension so that the extension knows how to validate all the Azure Pipelines tasks available in your Azure DevOps organization. That is not really a tip because it is a well-documented feature of the extension, I just did not take the time to read the documentation to find out about it 😅.",{"type":415,"tag":416,"props":11158,"children":11159},{},[11160],{"type":415,"tag":1322,"props":11161,"children":11165},{"alt":11162,"className":11163,"src":11164},"Documentation of the Azure Pipelines vscode extension about YAML schema.",[1326,1327],"/posts/images/w092021tips_vscodeextension_1.png",[],{"type":415,"tag":416,"props":11167,"children":11168},{},[11169,11171,11177],{"type":420,"value":11170},"As you can see in the documentation, you just have to download the custom schema of your DevOps organization which is located at this URL ",{"type":415,"tag":550,"props":11172,"children":11174},{"className":11173},[],[11175],{"type":420,"value":11176},"https://dev.azure.com/YOU-ORG-HERE/_apis/distributedtask/yamlschema",{"type":420,"value":11178},", reference it in you vs code workspace settings to make it work and enjoy IntelliSense on custom tasks.",{"type":415,"tag":416,"props":11180,"children":11181},{},[11182],{"type":420,"value":2446},{"type":415,"tag":1420,"props":11184,"children":11185},{},[11186],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":11188},[11189,11190,11191],{"id":10997,"depth":796,"text":11000},{"id":11063,"depth":796,"text":11066},{"id":11134,"depth":796,"text":11137},"content:1.posts:13.w09-2021-tips-learned-this-week.md","1.posts/13.w09-2021-tips-learned-this-week.md",{"_path":25,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":24,"description":11195,"lead":11196,"date":11197,"image":11198,"badge":11200,"tags":11201,"body":11202,"_type":1435,"_id":11769,"_source":1437,"_file":11770,"_extension":1439},"When working on a git repository, I often have to manually delete old local branches that I don't use anymore. That's not a huge waste of time but still, that's something I have to do quite often so I decided to automate that.","Playing with Nushell to create a useful git alias to delete unused local git branches.","2020-04-06T00:00:00.000Z",{"src":11199},"/images/branches_1.jpg",{"label":2210},[206,241,230,243],{"type":412,"children":11203,"toc":11763},[11204,11208,11214,11219,11232,11263,11268,11281,11287,11306,11311,11321,11342,11351,11364,11395,11401,11421,11430,11451,11460,11471,11480,11485,11656,11662,11681,11711,11732,11745,11754,11759],{"type":415,"tag":416,"props":11205,"children":11206},{},[11207],{"type":420,"value":11195},{"type":415,"tag":443,"props":11209,"children":11211},{"id":11210},"why-do-i-end-up-having-outdated-local-branches-on-my-git-repositories",[11212],{"type":420,"value":11213},"Why do I end up having outdated local branches on my git repositories?",{"type":415,"tag":416,"props":11215,"children":11216},{},[11217],{"type":420,"value":11218},"First, let's talk about how I end up having many useless local git branches. That's something quite usual and directly linked with the way I work with git but chances are that you are having the same issue.",{"type":415,"tag":416,"props":11220,"children":11221},{},[11222,11224,11230],{"type":420,"value":11223},"At work I am working in a small team of developers, we host our git repositories in ",{"type":415,"tag":423,"props":11225,"children":11228},{"href":11226,"rel":11227},"https://azure.microsoft.com/en-us/services/devops/repos/",[427],[11229],{"type":420,"value":343},{"type":420,"value":11231}," and we try to respect the following practices in our daily development:",{"type":415,"tag":493,"props":11233,"children":11234},{},[11235,11240,11245],{"type":415,"tag":497,"props":11236,"children":11237},{},[11238],{"type":420,"value":11239},"having a main branch (master) on which nobody can commit directly",{"type":415,"tag":497,"props":11241,"children":11242},{},[11243],{"type":420,"value":11244},"always create a short-lived branch (also called feature branch) when developing a new feature of the application",{"type":415,"tag":497,"props":11246,"children":11247},{},[11248,11250],{"type":420,"value":11249},"only merge a feature branch on the main branch through an Azure DevOps pull request\n",{"type":415,"tag":493,"props":11251,"children":11252},{},[11253,11258],{"type":415,"tag":497,"props":11254,"children":11255},{},[11256],{"type":420,"value":11257},"the PR triggers a pipeline that ensures the code build correctly, follow some conventions (with a Sonar analysis for instance) and that unit tests pass",{"type":415,"tag":497,"props":11259,"children":11260},{},[11261],{"type":420,"value":11262},"the PR can only be completed after a code review of at least one member of the team",{"type":415,"tag":416,"props":11264,"children":11265},{},[11266],{"type":420,"value":11267},"These practices allow us to keep good quality in our code base, not to mess with our git repositories, and ensure the main branch always builds.",{"type":415,"tag":416,"props":11269,"children":11270},{},[11271,11273,11279],{"type":420,"value":11272},"However, each week we are creating a lot of branches that need to be deleted as once merged we no longer need to have them. When a pull request is approved and we decide to complete it, Azure DevOps takes care of automatically merging the associated feature branch into master and deleting it from the repository. Once that's done, I can do a ",{"type":415,"tag":550,"props":11274,"children":11276},{"className":11275},[],[11277],{"type":420,"value":11278},"git fetch --prune",{"type":420,"value":11280}," on my laptop to have the feature branch removed from the remote of my local repository (by the way, I recommend you to directly set the fetch command to prune by default in your git config 👌). Nevertheless, this does not delete the local version of the feature branch thus our problem: over time if we do not think of deleting all these outdated branches, they become too many and we don't even know which branch should be kept or not.",{"type":415,"tag":443,"props":11282,"children":11284},{"id":11283},"git-commands-to-identify-and-delete-outdated-branches",[11285],{"type":420,"value":11286},"Git commands to identify and delete outdated branches.",{"type":415,"tag":416,"props":11288,"children":11289},{},[11290,11292,11297,11299,11305],{"type":420,"value":11291},"As my outdated branches are already removed from my remote (thanks to ",{"type":415,"tag":550,"props":11293,"children":11295},{"className":11294},[],[11296],{"type":420,"value":11278},{"type":420,"value":11298},") it should not be too complicated to use some git commands to guess which branches are not useful anymore. But as it's Azure DevOps that took care of merging them (sometimes with a squash) I cannot use the ",{"type":415,"tag":550,"props":11300,"children":11302},{"className":11301},[],[11303],{"type":420,"value":11304},"git branch --merged",{"type":420,"value":1864},{"type":415,"tag":416,"props":11307,"children":11308},{},[11309],{"type":420,"value":11310},"If I take my blog repository as an example I have a bunch of branches: some that could be useful (articles I have started to write but did not finish yet and I don't know if I will one day 😋) and some that are already merged into my master branch through a PR.",{"type":415,"tag":416,"props":11312,"children":11313},{},[11314],{"type":415,"tag":1322,"props":11315,"children":11320},{"alt":11316,"className":11317,"src":11318,"width":11319},"List all git branches in the terminal.",[1326,1327],"/posts/images/cleaningbranches_shell_1.png",800,[],{"type":415,"tag":416,"props":11322,"children":11323},{},[11324,11326,11332,11334,11340],{"type":420,"value":11325},"The command ",{"type":415,"tag":550,"props":11327,"children":11329},{"className":11328},[],[11330],{"type":420,"value":11331},"git branch -vl",{"type":420,"value":11333}," (which lists in a verbose way the local git branches) gives us an interesting view as it shows the branches for which the remote has been deleted specifying a ",{"type":415,"tag":550,"props":11335,"children":11337},{"className":11336},[],[11338],{"type":420,"value":11339},"[gone]",{"type":420,"value":11341}," for them. These branches correspond to the outdated branches we want to delete.",{"type":415,"tag":416,"props":11343,"children":11344},{},[11345],{"type":415,"tag":1322,"props":11346,"children":11350},{"alt":11347,"className":11348,"src":11349,"width":2413},"List all git branches with verbose tag in terminal.",[1326,1327],"/posts/images/cleaningbranches_shell_2.png",[],{"type":415,"tag":416,"props":11352,"children":11353},{},[11354,11356,11362],{"type":420,"value":11355},"We know how to identify the outdated branches but we need a command to delete them which is the ",{"type":415,"tag":550,"props":11357,"children":11359},{"className":11358},[],[11360],{"type":420,"value":11361},"git branch -D",{"type":420,"value":11363}," command. Now we only need a script to associate the output and input of these two commands to automate the deletion.",{"type":415,"tag":416,"props":11365,"children":11366},{},[11367,11369,11376,11378,11384,11386,11393],{"type":420,"value":11368},"You can find on Stackoverflow some posts like ",{"type":415,"tag":423,"props":11370,"children":11373},{"href":11371,"rel":11372},"https://stackoverflow.com/questions/7726949/remove-tracking-branches-no-longer-on-remote",[427],[11374],{"type":420,"value":11375},"this one",{"type":420,"value":11377}," that show different solutions using bash that work perfectly but I thought it would be interesting to try to script that using another shell. Indeed I recently started to use a shell called ",{"type":415,"tag":423,"props":11379,"children":11382},{"href":11380,"rel":11381},"https://github.com/nushell/nushell",[427],[11383],{"type":420,"value":243},{"type":420,"value":11385}," which is a pretty powerful yet simple cross-platform shell. It is still in preview at the time of writing but if you have not heard of it I suggest you read the ",{"type":415,"tag":423,"props":11387,"children":11390},{"href":11388,"rel":11389},"https://www.jonathanturner.org/2019/08/introducing-nushell.html",[427],[11391],{"type":420,"value":11392},"introduction post",{"type":420,"value":11394}," of Jonathan Turner.",{"type":415,"tag":443,"props":11396,"children":11398},{"id":11397},"lets-script-that-with-nushell",[11399],{"type":420,"value":11400},"Let's script that with nushell!",{"type":415,"tag":416,"props":11402,"children":11403},{},[11404,11406,11411,11413,11419],{"type":420,"value":11405},"Enough of talking, let's script.\nTo start with, we can use the nu lines command to create a table from the lines of the ",{"type":415,"tag":550,"props":11407,"children":11409},{"className":11408},[],[11410],{"type":420,"value":11331},{"type":420,"value":11412}," output (we added an extra ",{"type":415,"tag":550,"props":11414,"children":11416},{"className":11415},[],[11417],{"type":420,"value":11418},"*/*",{"type":420,"value":11420}," argument as we are only interested in posts branches).",{"type":415,"tag":416,"props":11422,"children":11423},{},[11424],{"type":415,"tag":1322,"props":11425,"children":11429},{"alt":11426,"className":11427,"src":11428,"width":2413},"List git branches in table in the terminal.",[1326,1327],"/posts/images/cleaningbranches_shell_3.png",[],{"type":415,"tag":416,"props":11431,"children":11432},{},[11433,11435,11441,11443,11449],{"type":420,"value":11434},"Then we can split the different lines into columns that we can name with the ",{"type":415,"tag":550,"props":11436,"children":11438},{"className":11437},[],[11439],{"type":420,"value":11440},"split column",{"type":420,"value":11442}," command. We use spaces to correctly split a line and the option ",{"type":415,"tag":550,"props":11444,"children":11446},{"className":11445},[],[11447],{"type":420,"value":11448},"--collapse-empty",{"type":420,"value":11450}," to remove the empty columns.",{"type":415,"tag":416,"props":11452,"children":11453},{},[11454],{"type":415,"tag":1322,"props":11455,"children":11459},{"alt":11456,"className":11457,"src":11458,"width":2413},"List git branches in table with columns by property in the terminal.",[1326,1327],"/posts/images/cleaningbranches_shell_4.png",[],{"type":415,"tag":416,"props":11461,"children":11462},{},[11463,11465,11470],{"type":420,"value":11464},"We then just have to filter the table to get only the lines with the Status ",{"type":415,"tag":550,"props":11466,"children":11468},{"className":11467},[],[11469],{"type":420,"value":11339},{"type":420,"value":755},{"type":415,"tag":416,"props":11472,"children":11473},{},[11474],{"type":415,"tag":1322,"props":11475,"children":11479},{"alt":11476,"className":11477,"src":11478,"width":2413},"Filter on git branches gone in the terminal.",[1326,1327],"/posts/images/cleaningbranches_shell_5.png",[],{"type":415,"tag":416,"props":11481,"children":11482},{},[11483],{"type":420,"value":11484},"And the final script:",{"type":415,"tag":773,"props":11486,"children":11488},{"className":1686,"code":11487,"language":1688,"meta":401,"style":401},"git branch -vl '*/*' | lines | split column \" \" BranchName Hash Status --collapse-empty | where Status == '[gone]' | each { |it| git branch -D $it.BranchName }\n",[11489],{"type":415,"tag":550,"props":11490,"children":11491},{"__ignoreMap":401},[11492],{"type":415,"tag":783,"props":11493,"children":11494},{"class":785,"line":786},[11495,11499,11504,11509,11513,11517,11521,11525,11530,11534,11539,11544,11548,11552,11557,11562,11567,11572,11576,11581,11585,11590,11594,11598,11602,11606,11611,11615,11619,11623,11627,11632,11636,11641,11646,11651],{"type":415,"tag":783,"props":11496,"children":11497},{"style":875},[11498],{"type":420,"value":241},{"type":415,"tag":783,"props":11500,"children":11501},{"style":825},[11502],{"type":420,"value":11503}," branch",{"type":415,"tag":783,"props":11505,"children":11506},{"style":825},[11507],{"type":420,"value":11508}," -vl",{"type":415,"tag":783,"props":11510,"children":11511},{"style":790},[11512],{"type":420,"value":9177},{"type":415,"tag":783,"props":11514,"children":11515},{"style":825},[11516],{"type":420,"value":11418},{"type":415,"tag":783,"props":11518,"children":11519},{"style":790},[11520],{"type":420,"value":9187},{"type":415,"tag":783,"props":11522,"children":11523},{"style":790},[11524],{"type":420,"value":5490},{"type":415,"tag":783,"props":11526,"children":11527},{"style":875},[11528],{"type":420,"value":11529}," lines",{"type":415,"tag":783,"props":11531,"children":11532},{"style":790},[11533],{"type":420,"value":5490},{"type":415,"tag":783,"props":11535,"children":11536},{"style":875},[11537],{"type":420,"value":11538}," split",{"type":415,"tag":783,"props":11540,"children":11541},{"style":825},[11542],{"type":420,"value":11543}," column",{"type":415,"tag":783,"props":11545,"children":11546},{"style":790},[11547],{"type":420,"value":822},{"type":415,"tag":783,"props":11549,"children":11550},{"style":790},[11551],{"type":420,"value":822},{"type":415,"tag":783,"props":11553,"children":11554},{"style":825},[11555],{"type":420,"value":11556}," BranchName",{"type":415,"tag":783,"props":11558,"children":11559},{"style":825},[11560],{"type":420,"value":11561}," Hash",{"type":415,"tag":783,"props":11563,"children":11564},{"style":825},[11565],{"type":420,"value":11566}," Status",{"type":415,"tag":783,"props":11568,"children":11569},{"style":825},[11570],{"type":420,"value":11571}," --collapse-empty",{"type":415,"tag":783,"props":11573,"children":11574},{"style":790},[11575],{"type":420,"value":5490},{"type":415,"tag":783,"props":11577,"children":11578},{"style":875},[11579],{"type":420,"value":11580}," where",{"type":415,"tag":783,"props":11582,"children":11583},{"style":825},[11584],{"type":420,"value":11566},{"type":415,"tag":783,"props":11586,"children":11587},{"style":825},[11588],{"type":420,"value":11589}," ==",{"type":415,"tag":783,"props":11591,"children":11592},{"style":790},[11593],{"type":420,"value":9177},{"type":415,"tag":783,"props":11595,"children":11596},{"style":825},[11597],{"type":420,"value":11339},{"type":415,"tag":783,"props":11599,"children":11600},{"style":790},[11601],{"type":420,"value":9187},{"type":415,"tag":783,"props":11603,"children":11604},{"style":790},[11605],{"type":420,"value":5490},{"type":415,"tag":783,"props":11607,"children":11608},{"style":875},[11609],{"type":420,"value":11610}," each",{"type":415,"tag":783,"props":11612,"children":11613},{"style":825},[11614],{"type":420,"value":5431},{"type":415,"tag":783,"props":11616,"children":11617},{"style":790},[11618],{"type":420,"value":5490},{"type":415,"tag":783,"props":11620,"children":11621},{"style":875},[11622],{"type":420,"value":268},{"type":415,"tag":783,"props":11624,"children":11625},{"style":790},[11626],{"type":420,"value":4216},{"type":415,"tag":783,"props":11628,"children":11629},{"style":875},[11630],{"type":420,"value":11631}," git",{"type":415,"tag":783,"props":11633,"children":11634},{"style":825},[11635],{"type":420,"value":11503},{"type":415,"tag":783,"props":11637,"children":11638},{"style":825},[11639],{"type":420,"value":11640}," -D",{"type":415,"tag":783,"props":11642,"children":11643},{"style":1362},[11644],{"type":420,"value":11645}," $it",{"type":415,"tag":783,"props":11647,"children":11648},{"style":825},[11649],{"type":420,"value":11650},".BranchName",{"type":415,"tag":783,"props":11652,"children":11653},{"style":825},[11654],{"type":420,"value":11655}," }\n",{"type":415,"tag":443,"props":11657,"children":11659},{"id":11658},"make-it-a-git-alias",[11660],{"type":420,"value":11661},"Make it a git alias.",{"type":415,"tag":416,"props":11663,"children":11664},{},[11665,11667,11673,11675,11680],{"type":420,"value":11666},"We can integrate this script into our git commands by creating a git alias. Let's say I want to create the alias ",{"type":415,"tag":550,"props":11668,"children":11670},{"className":11669},[],[11671],{"type":420,"value":11672},"bcl",{"type":420,"value":11674}," for branch clean up, we only need to add the following to our ",{"type":415,"tag":550,"props":11676,"children":11678},{"className":11677},[],[11679],{"type":420,"value":3313},{"type":420,"value":635},{"type":415,"tag":773,"props":11682,"children":11684},{"className":6776,"code":11683,"language":6778,"meta":401,"style":401},"[alias]\n    bcl = !nu \\\"D:\\\\gitalias_bcl.nu\\\"\n",[11685],{"type":415,"tag":550,"props":11686,"children":11687},{"__ignoreMap":401},[11688,11703],{"type":415,"tag":783,"props":11689,"children":11690},{"class":785,"line":786},[11691,11695,11699],{"type":415,"tag":783,"props":11692,"children":11693},{"style":790},[11694],{"type":420,"value":6790},{"type":415,"tag":783,"props":11696,"children":11697},{"style":825},[11698],{"type":420,"value":6795},{"type":415,"tag":783,"props":11700,"children":11701},{"style":790},[11702],{"type":420,"value":6800},{"type":415,"tag":783,"props":11704,"children":11705},{"class":785,"line":796},[11706],{"type":415,"tag":783,"props":11707,"children":11708},{"style":825},[11709],{"type":420,"value":11710},"    bcl = !nu \\\"D:\\\\gitalias_bcl.nu\\\"\n",{"type":415,"tag":416,"props":11712,"children":11713},{},[11714,11716,11722,11724,11730],{"type":420,"value":11715},"where ",{"type":415,"tag":550,"props":11717,"children":11719},{"className":11718},[],[11720],{"type":420,"value":11721},"gitalias_bcl.nu",{"type":420,"value":11723}," is the nu script file we created earlier (it's located here in the ",{"type":415,"tag":550,"props":11725,"children":11727},{"className":11726},[],[11728],{"type":420,"value":11729},"D://",{"type":420,"value":11731}," drive but can be created anywhere).",{"type":415,"tag":416,"props":11733,"children":11734},{},[11735,11737,11743],{"type":420,"value":11736},"Now we can simply do a ",{"type":415,"tag":550,"props":11738,"children":11740},{"className":11739},[],[11741],{"type":420,"value":11742},"git bcl",{"type":420,"value":11744}," to clean our outdated local git branches.",{"type":415,"tag":416,"props":11746,"children":11747},{},[11748],{"type":415,"tag":1322,"props":11749,"children":11753},{"alt":11750,"className":11751,"src":11752,"width":11319},"List oudated git branches  in the terminal.",[1326,1327],"/posts/images/cleaningbranches_shell_6.png",[],{"type":415,"tag":416,"props":11755,"children":11756},{},[11757],{"type":420,"value":11758},"That's it, nothing revolutionary but that was the opportunity to automate the boring task of deleting outdated local branches while playing with nushell.",{"type":415,"tag":1420,"props":11760,"children":11761},{},[11762],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":11764},[11765,11766,11767,11768],{"id":11210,"depth":796,"text":11213},{"id":11283,"depth":796,"text":11286},{"id":11397,"depth":796,"text":11400},{"id":11658,"depth":796,"text":11661},"content:1.posts:6.cleaning-git-branches.md","1.posts/6.cleaning-git-branches.md",{"_path":19,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":18,"description":11772,"lead":11773,"date":11774,"image":11775,"tags":11777,"badge":11778,"body":11779,"_type":1435,"_id":12038,"_source":1437,"_file":12039,"_extension":1439},"The other day when I was looking for a way to automate my development environment setup, I came across Gitpod. Not really what I was looking for but I discovered an awesome tool for working on open source projects.","An IDE in the browser","2019-08-12T00:00:00.000Z",{"src":11776},"/images/github-usbkey.jpg",[233,206,208],{"label":408},{"type":412,"children":11780,"toc":12036},[11781,11795,11800,11829,11834,11845,11868,11897,11912,11917,11932,11937,11942,11951,11969,11987,11992,12031],{"type":415,"tag":416,"props":11782,"children":11783},{},[11784,11786,11793],{"type":420,"value":11785},"The other day when I was looking for a way to automate my development environment setup, I came across ",{"type":415,"tag":423,"props":11787,"children":11790},{"href":11788,"rel":11789},"https://www.gitpod.io/",[427],[11791],{"type":420,"value":11792},"Gitpod",{"type":420,"value":11794},". Not really what I was looking for but I discovered an awesome tool for working on open source projects.",{"type":415,"tag":416,"props":11796,"children":11797},{},[11798],{"type":420,"value":11799},"Gitpod is an online service that aims at providing ready-to-code development environments in the browser for GitHub projects.",{"type":415,"tag":416,"props":11801,"children":11802},{},[11803,11805,11812,11814,11820,11822,11828],{"type":420,"value":11804},"Let's see that with one project I was contributing to a few months ago: the ",{"type":415,"tag":423,"props":11806,"children":11809},{"href":11807,"rel":11808},"https://github.com/voxxrin/voxxrin-companion-ui",[427],[11810],{"type":420,"value":11811},"Voxxrin Companion mobile app",{"type":420,"value":11813},".\nFirst, let's go to the project GitHub's page, and prefix the URL of the page with ",{"type":415,"tag":550,"props":11815,"children":11817},{"className":11816},[],[11818],{"type":420,"value":11819},"gitpod.io/#",{"type":420,"value":11821}," which gives us the following URL: ",{"type":415,"tag":423,"props":11823,"children":11826},{"href":11824,"rel":11825},"http://gitpod.io/#https://github.com/voxxrin/voxxrin-companion-ui",[427],[11827],{"type":420,"value":11824},{"type":420,"value":755},{"type":415,"tag":416,"props":11830,"children":11831},{},[11832],{"type":420,"value":11833},"As I am already logged in to the  Gitpod account I created (free for open source developers with a quota of 100 hours/month) Gitpod will start creating a development environment for me. That means pulling a Docker image with everything I need to code (for specific needs, you can also specify a custom docker image to use) and initializing a workspace for me with the code of the project.",{"type":415,"tag":416,"props":11835,"children":11836},{},[11837,11839],{"type":420,"value":11838},"Less than 30s and here we are :\n",{"type":415,"tag":1322,"props":11840,"children":11844},{"alt":11841,"className":11842,"src":11843},"Voxxr.in project opened in gitpod in browser.",[1326,1327],"/posts/images/gitpod_voxxrin_1.png",[],{"type":415,"tag":416,"props":11846,"children":11847},{},[11848,11850,11857,11859,11866],{"type":420,"value":11849},"I have now a complete development environment running in the cloud with the code of my project already cloned. If the editor looks familiar to you it's because ",{"type":415,"tag":423,"props":11851,"children":11854},{"href":11852,"rel":11853},"https://www.theia-ide.org/",[427],[11855],{"type":420,"value":11856},"Theia",{"type":420,"value":11858}," (the IDE's name) is very similar to ",{"type":415,"tag":423,"props":11860,"children":11863},{"href":11861,"rel":11862},"http://typefox.io/theia-vs-code-in-the-cloud",[427],[11864],{"type":420,"value":11865},"VS Code but in the cloud",{"type":420,"value":11867}," (it reuses many parts from VS Code). This is great because it means I can use an IDE I am already familiar with, and even use vs code extensions (provided you have the .vsix files to install them).",{"type":415,"tag":416,"props":11869,"children":11870},{},[11871,11873,11879,11881,11887,11889,11895],{"type":420,"value":11872},"So let's run my project which is an ionic application. Node is already installed so I just have to type ",{"type":415,"tag":550,"props":11874,"children":11876},{"className":11875},[],[11877],{"type":420,"value":11878},"npm install",{"type":420,"value":11880}," and then ",{"type":415,"tag":550,"props":11882,"children":11884},{"className":11883},[],[11885],{"type":420,"value":11886},"npm run-script serve",{"type":420,"value":11888}," in the terminal opened in the editor exactly as if I was working on my local VS Code. My application is configured to be launched on port ",{"type":415,"tag":550,"props":11890,"children":11892},{"className":11891},[],[11893],{"type":420,"value":11894},"8100",{"type":420,"value":11896}," so Gitpod will suggest I expose this port for me to be able to view my application in my browser.",{"type":415,"tag":416,"props":11898,"children":11899},{},[11900,11906],{"type":415,"tag":1322,"props":11901,"children":11905},{"alt":11902,"className":11903,"src":11904},"GitPod suggestion of opening port to run the application.",[1326,1327],"/posts/images/gitpod_voxxrin_2.png",[],{"type":415,"tag":1322,"props":11907,"children":11911},{"alt":11908,"className":11909,"src":11910},"GitPod suggestion of opening live preview of the application.",[1326,1327],"/posts/images/gitpod_voxxrin_3.png",[],{"type":415,"tag":416,"props":11913,"children":11914},{},[11915],{"type":420,"value":11916},"I can now open in my computer browser the application running in my Gitpod workspace. I can also preview it directly in Gitpod.",{"type":415,"tag":416,"props":11918,"children":11919},{},[11920,11926],{"type":415,"tag":1322,"props":11921,"children":11925},{"alt":11922,"className":11923,"src":11924},"Voxxr.in application served in a browser tab.",[1326,1327],"/posts/images/gitpod_voxxrin_5.png",[],{"type":415,"tag":1322,"props":11927,"children":11931},{"alt":11928,"className":11929,"src":11930},"Voxxr.in application served in GitPod tab.",[1326,1327],"/posts/images/gitpod_voxxrin_4.png",[],{"type":415,"tag":416,"props":11933,"children":11934},{},[11935],{"type":420,"value":11936},"If I have some modifications to do I can push them directly from here to my GitHub repository as my Gitpod account is associated with my GitHub account.",{"type":415,"tag":416,"props":11938,"children":11939},{},[11940],{"type":420,"value":11941},"When I have finished, I can just stop the workspace and that's it.",{"type":415,"tag":416,"props":11943,"children":11944},{},[11945],{"type":415,"tag":1322,"props":11946,"children":11950},{"alt":11947,"className":11948,"src":11949},"GitPod menu to stop the workspace.",[1326,1327],"/posts/images/gitpod_voxxrin_7.png",[],{"type":415,"tag":416,"props":11952,"children":11953},{},[11954,11956,11967],{"type":420,"value":11955},"I created the workspace for my project as-is but lots of things can be configured by having a ",{"type":415,"tag":423,"props":11957,"children":11960},{"href":11958,"rel":11959},"https://www.gitpod.io/docs/41_config_gitpod_file/",[427],[11961],{"type":415,"tag":550,"props":11962,"children":11964},{"className":11963},[],[11965],{"type":420,"value":11966},"gitpod.yml",{"type":420,"value":11968}," file in the repository of your project",{"type":415,"tag":416,"props":11970,"children":11971},{},[11972,11974,11985],{"type":420,"value":11973},"With Gitpod you just need a browser and an internet connection to be able to work on any of your GitHub projects from anywhere. When you work on multiple open source projects the nice thing is that it allows you to easily switch from one to another. I wonder if the Browser-Based Web Companion ",{"type":415,"tag":423,"props":11975,"children":11978},{"href":11976,"rel":11977},"https://devblogs.microsoft.com/visualstudio/intelligent-productivity-and-collaboration-from-anywhere/",[427],[11979],{"type":415,"tag":11980,"props":11981,"children":11982},"strong",{},[11983],{"type":420,"value":11984},"Visual Studio Online",{"type":420,"value":11986}," announced by Microsoft at Build 2019 will offer the same capabilities and how it will differ from Gitpod.",{"type":415,"tag":416,"props":11988,"children":11989},{},[11990],{"type":420,"value":11991},"This was just a quick introduction to Gitpod but there are far more things you can do with it:",{"type":415,"tag":493,"props":11993,"children":11994},{},[11995,12006,12011,12016,12021,12026],{"type":415,"tag":497,"props":11996,"children":11997},{},[11998,12000,12005],{"type":420,"value":11999},"running scripts on the start-up of your environment  (an ",{"type":415,"tag":550,"props":12001,"children":12003},{"className":12002},[],[12004],{"type":420,"value":11878},{"type":420,"value":4547},{"type":415,"tag":497,"props":12007,"children":12008},{},[12009],{"type":420,"value":12010},"continuously auto-building your repository",{"type":415,"tag":497,"props":12012,"children":12013},{},[12014],{"type":420,"value":12015},"install a database",{"type":415,"tag":497,"props":12017,"children":12018},{},[12019],{"type":420,"value":12020},"use it for private projects (not free, there are multiple pricings available)",{"type":415,"tag":497,"props":12022,"children":12023},{},[12024],{"type":420,"value":12025},"share an environment with other users",{"type":415,"tag":497,"props":12027,"children":12028},{},[12029],{"type":420,"value":12030},"...",{"type":415,"tag":416,"props":12032,"children":12033},{},[12034],{"type":420,"value":12035},"But the best is you try and judge by yourself.",{"title":401,"searchDepth":796,"depth":796,"links":12037},[],"content:1.posts:4.gitpod.md","1.posts/4.gitpod.md",{"_path":10,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":9,"description":12041,"lead":12042,"date":12043,"image":12044,"badge":12046,"tags":12047,"body":12048,"_type":1435,"_id":12912,"_source":1437,"_file":12913,"_extension":1439},"Let's talk about tooling and testing an API!","Why using the vscode extension \"REST Client\" instead of Postman?","2019-03-05T00:00:00.000Z",{"src":12045},"/images/swiss_knifes.jpg",{"label":408},[206,208,210,213],{"type":412,"children":12049,"toc":12905},[12050,12054,12060,12065,12074,12079,12090,12096,12105,12135,12140,12154,12183,12193,12199,12233,12242,12247,12485,12530,12539,12545,12550,12555,12560,12566,12571,12583,12588,12901],{"type":415,"tag":416,"props":12051,"children":12052},{},[12053],{"type":420,"value":12041},{"type":415,"tag":443,"props":12055,"children":12057},{"id":12056},"gui-tools-and-their-limits",[12058],{"type":420,"value":12059},"GUI Tools and their limits",{"type":415,"tag":416,"props":12061,"children":12062},{},[12063],{"type":420,"value":12064},"Like most developers I guess, I often use GUI tools like Fiddler or Postman to query an API.\nOnce you get used to the HMI of Postman with all its tabs, it's quite easy to create GET / POST / PUT / ... requests, save them in a collection, and visualize the answers. Postman offers a lot of other features, but a very handy one is the possibility to use environment variables in your requests.",{"type":415,"tag":416,"props":12066,"children":12067},{},[12068],{"type":415,"tag":1322,"props":12069,"children":12073},{"alt":12070,"className":12071,"src":12072},"Postman UI",[1326,1327],"/posts/images/restclient_postman_1.png",[],{"type":415,"tag":416,"props":12075,"children":12076},{},[12077],{"type":420,"value":12078},"Seems to be the perfect tool you would say. Well, that's true if you want to quickly test an API on your own, and to be honest I use it a lot for that. But when you are collaborating on a real project with other developers, there might be some things you will miss. For me, it's a way to edit, version, and share requests with other developers.",{"type":415,"tag":416,"props":12080,"children":12081},{},[12082,12084,12089],{"type":420,"value":12083},"Okay, I know that Postman paid plan allows you to share requests with other people of your team. And I am aware of the collection export feature on the free plan, but that's not enough. I don't really like to be too tied to a proprietary software and its GUI to do things. As a developer what I want is to be able to edit my requests in simple text files that I can put in version control. And here's come ",{"type":415,"tag":423,"props":12085,"children":12087},{"href":2508,"rel":12086},[427],[12088],{"type":420,"value":2512},{"type":420,"value":755},{"type":415,"tag":443,"props":12091,"children":12093},{"id":12092},"what-is-rest-client",[12094],{"type":420,"value":12095},"What is REST Client?",{"type":415,"tag":416,"props":12097,"children":12098},{},[12099],{"type":415,"tag":1322,"props":12100,"children":12104},{"alt":12101,"className":12102,"src":12103},"REST Client extension in vscode marketplace",[1326,1327],"/posts/images/restclient_vscode_1.png",[],{"type":415,"tag":416,"props":12106,"children":12107},{},[12108,12110,12117,12119,12125,12127,12134],{"type":420,"value":12109},"The REST Client extension is an open source vs code extension developed by ",{"type":415,"tag":423,"props":12111,"children":12114},{"href":12112,"rel":12113},"https://github.com/Huachao",[427],[12115],{"type":420,"value":12116},"Huachao Mao",{"type":420,"value":12118},". If I quote the README of its ",{"type":415,"tag":423,"props":12120,"children":12122},{"href":7308,"rel":12121},[427],[12123],{"type":420,"value":12124},"GitHub repository",{"type":420,"value":12126},": REST Client allows you to send HTTP request and view the response in Visual Studio Code directly.\nLet's see that with a simple GET request to ",{"type":415,"tag":423,"props":12128,"children":12131},{"href":12129,"rel":12130},"https://swapi.co/",[427],[12132],{"type":420,"value":12133},"The Start Wars API",{"type":420,"value":635},{"type":415,"tag":416,"props":12136,"children":12137},{},[12138],{"type":420,"value":12139},"05578273",{"type":415,"tag":416,"props":12141,"children":12142},{},[12143,12145,12152],{"type":420,"value":12144},"Nothing new or complicated here, just the request you would have written intuitively. Like this, you can write any kind of request you want simply following the standard RFC 2616. Even if you don't know the standard, it's pretty straightforward and you often find samples with this format on the documentation of the API you are querying, like on the ",{"type":415,"tag":423,"props":12146,"children":12149},{"href":12147,"rel":12148},"https://docs.microsoft.com/en-us/graph/api/user-list-memberof?view=graph-rest-1.0#example",[427],[12150],{"type":420,"value":12151},"Microsoft Graph API documentation",{"type":420,"value":12153}," for instance.",{"type":415,"tag":416,"props":12155,"children":12156},{},[12157,12159,12163,12165,12169,12170,12174,12176,12181],{"type":420,"value":12158},"REST Client works on text files in vscode by selecting ",{"type":415,"tag":2746,"props":12160,"children":12161},{},[12162],{"type":420,"value":213},{"type":420,"value":12164}," as the Language Mode (by default this language mode is associated with files having the ",{"type":415,"tag":2746,"props":12166,"children":12167},{},[12168],{"type":420,"value":2750},{"type":420,"value":2752},{"type":415,"tag":2746,"props":12171,"children":12172},{},[12173],{"type":420,"value":2757},{"type":420,"value":12175}," extension). It provides you with some autocompletion and a few snippets to help you write your queries. You can write multiple requests on the same file in vscode just by separating them with ###. Above each request an actionable ",{"type":415,"tag":2746,"props":12177,"children":12178},{},[12179],{"type":420,"value":12180},"Send Request",{"type":420,"value":12182}," link allows you to run the request and see the response in a response panel.",{"type":415,"tag":416,"props":12184,"children":12185},{},[12186],{"type":415,"tag":1322,"props":12187,"children":12192},{"alt":12188,"className":12189,"src":12190,"height":12191,"width":11319},"Intellisense in HTTP files in vscode",[1326,1327],"/posts/images/restclient_swapi_3.png",400,[],{"type":415,"tag":443,"props":12194,"children":12196},{"id":12195},"using-variables-in-rest-client",[12197],{"type":420,"value":12198},"Using variables in REST Client",{"type":415,"tag":416,"props":12200,"children":12201},{},[12202,12204,12210,12212,12223,12225,12231],{"type":420,"value":12203},"As you can see below, it is possible to use variables with REST Client. A variable ",{"type":415,"tag":550,"props":12205,"children":12207},{"className":12206},[],[12208],{"type":420,"value":12209},"planetName",{"type":420,"value":12211}," is defined in the file and reused in 2 requests. A variable is also used to name the request ",{"type":415,"tag":2746,"props":12213,"children":12214},{},[12215,12217],{"type":420,"value":12216},"GET ",{"type":415,"tag":423,"props":12218,"children":12221},{"href":12219,"rel":12220},"https://swapi.co/api/people/?search=Luke",[427],[12222],{"type":420,"value":12219},{"type":420,"value":12224}," and makes it possible to use elements from the response (that you can see on the right). Here we are using the ",{"type":415,"tag":550,"props":12226,"children":12228},{"className":12227},[],[12229],{"type":420,"value":12230},"homeworld",{"type":420,"value":12232}," property of the response to retrieve the planet from which Luke was from in the following GET request. With the help of variables you can easily combine and chain requests for the scenario you need to realize.",{"type":415,"tag":416,"props":12234,"children":12235},{},[12236],{"type":415,"tag":1322,"props":12237,"children":12241},{"alt":12238,"className":12239,"src":12240},"Execution of an HTTP request in vscode",[1326,1327],"/posts/images/restclient_swapi_2.png",[],{"type":415,"tag":416,"props":12243,"children":12244},{},[12245],{"type":420,"value":12246},"REST Client allows you to define environments and their associated variables in the user settings file of vscode. For instance, let's say I want to query the Microsoft Graph API (both the V1 version and the beta version of the API), I will add the following JSON to my  settings file:",{"type":415,"tag":773,"props":12248,"children":12250},{"className":775,"code":12249,"language":777,"meta":401,"style":401},"\"rest-client.environmentVariables\": {\n    \"$shared\": {\n        \"host\": \"https://graph.microsoft.com/\",\n    },\n    \"graphV1\": {\n        \"version\": \"v1.0\",\n    },\n    \"graphBeta\": {\n        \"version\": \"beta\"\n    },\n}\n",[12251],{"type":415,"tag":550,"props":12252,"children":12253},{"__ignoreMap":401},[12254,12278,12302,12339,12347,12371,12408,12415,12439,12471,12478],{"type":415,"tag":783,"props":12255,"children":12256},{"class":785,"line":786},[12257,12261,12266,12270,12274],{"type":415,"tag":783,"props":12258,"children":12259},{"style":790},[12260],{"type":420,"value":813},{"type":415,"tag":783,"props":12262,"children":12263},{"style":825},[12264],{"type":420,"value":12265},"rest-client.environmentVariables",{"type":415,"tag":783,"props":12267,"children":12268},{"style":790},[12269],{"type":420,"value":813},{"type":415,"tag":783,"props":12271,"children":12272},{"style":1362},[12273],{"type":420,"value":11038},{"type":415,"tag":783,"props":12275,"children":12276},{"style":790},[12277],{"type":420,"value":793},{"type":415,"tag":783,"props":12279,"children":12280},{"class":785,"line":796},[12281,12285,12290,12294,12298],{"type":415,"tag":783,"props":12282,"children":12283},{"style":790},[12284],{"type":420,"value":872},{"type":415,"tag":783,"props":12286,"children":12287},{"style":805},[12288],{"type":420,"value":12289},"$shared",{"type":415,"tag":783,"props":12291,"children":12292},{"style":790},[12293],{"type":420,"value":813},{"type":415,"tag":783,"props":12295,"children":12296},{"style":790},[12297],{"type":420,"value":635},{"type":415,"tag":783,"props":12299,"children":12300},{"style":790},[12301],{"type":420,"value":863},{"type":415,"tag":783,"props":12303,"children":12304},{"class":785,"line":840},[12305,12309,12314,12318,12322,12326,12331,12335],{"type":415,"tag":783,"props":12306,"children":12307},{"style":790},[12308],{"type":420,"value":926},{"type":415,"tag":783,"props":12310,"children":12311},{"style":875},[12312],{"type":420,"value":12313},"host",{"type":415,"tag":783,"props":12315,"children":12316},{"style":790},[12317],{"type":420,"value":813},{"type":415,"tag":783,"props":12319,"children":12320},{"style":790},[12321],{"type":420,"value":635},{"type":415,"tag":783,"props":12323,"children":12324},{"style":790},[12325],{"type":420,"value":822},{"type":415,"tag":783,"props":12327,"children":12328},{"style":825},[12329],{"type":420,"value":12330},"https://graph.microsoft.com/",{"type":415,"tag":783,"props":12332,"children":12333},{"style":790},[12334],{"type":420,"value":813},{"type":415,"tag":783,"props":12336,"children":12337},{"style":790},[12338],{"type":420,"value":837},{"type":415,"tag":783,"props":12340,"children":12341},{"class":785,"line":866},[12342],{"type":415,"tag":783,"props":12343,"children":12344},{"style":790},[12345],{"type":420,"value":12346},"    },\n",{"type":415,"tag":783,"props":12348,"children":12349},{"class":785,"line":893},[12350,12354,12359,12363,12367],{"type":415,"tag":783,"props":12351,"children":12352},{"style":790},[12353],{"type":420,"value":872},{"type":415,"tag":783,"props":12355,"children":12356},{"style":805},[12357],{"type":420,"value":12358},"graphV1",{"type":415,"tag":783,"props":12360,"children":12361},{"style":790},[12362],{"type":420,"value":813},{"type":415,"tag":783,"props":12364,"children":12365},{"style":790},[12366],{"type":420,"value":635},{"type":415,"tag":783,"props":12368,"children":12369},{"style":790},[12370],{"type":420,"value":863},{"type":415,"tag":783,"props":12372,"children":12373},{"class":785,"line":920},[12374,12378,12383,12387,12391,12395,12400,12404],{"type":415,"tag":783,"props":12375,"children":12376},{"style":790},[12377],{"type":420,"value":926},{"type":415,"tag":783,"props":12379,"children":12380},{"style":875},[12381],{"type":420,"value":12382},"version",{"type":415,"tag":783,"props":12384,"children":12385},{"style":790},[12386],{"type":420,"value":813},{"type":415,"tag":783,"props":12388,"children":12389},{"style":790},[12390],{"type":420,"value":635},{"type":415,"tag":783,"props":12392,"children":12393},{"style":790},[12394],{"type":420,"value":822},{"type":415,"tag":783,"props":12396,"children":12397},{"style":825},[12398],{"type":420,"value":12399},"v1.0",{"type":415,"tag":783,"props":12401,"children":12402},{"style":790},[12403],{"type":420,"value":813},{"type":415,"tag":783,"props":12405,"children":12406},{"style":790},[12407],{"type":420,"value":837},{"type":415,"tag":783,"props":12409,"children":12410},{"class":785,"line":959},[12411],{"type":415,"tag":783,"props":12412,"children":12413},{"style":790},[12414],{"type":420,"value":12346},{"type":415,"tag":783,"props":12416,"children":12417},{"class":785,"line":997},[12418,12422,12427,12431,12435],{"type":415,"tag":783,"props":12419,"children":12420},{"style":790},[12421],{"type":420,"value":872},{"type":415,"tag":783,"props":12423,"children":12424},{"style":805},[12425],{"type":420,"value":12426},"graphBeta",{"type":415,"tag":783,"props":12428,"children":12429},{"style":790},[12430],{"type":420,"value":813},{"type":415,"tag":783,"props":12432,"children":12433},{"style":790},[12434],{"type":420,"value":635},{"type":415,"tag":783,"props":12436,"children":12437},{"style":790},[12438],{"type":420,"value":863},{"type":415,"tag":783,"props":12440,"children":12441},{"class":785,"line":1023},[12442,12446,12450,12454,12458,12462,12467],{"type":415,"tag":783,"props":12443,"children":12444},{"style":790},[12445],{"type":420,"value":926},{"type":415,"tag":783,"props":12447,"children":12448},{"style":875},[12449],{"type":420,"value":12382},{"type":415,"tag":783,"props":12451,"children":12452},{"style":790},[12453],{"type":420,"value":813},{"type":415,"tag":783,"props":12455,"children":12456},{"style":790},[12457],{"type":420,"value":635},{"type":415,"tag":783,"props":12459,"children":12460},{"style":790},[12461],{"type":420,"value":822},{"type":415,"tag":783,"props":12463,"children":12464},{"style":825},[12465],{"type":420,"value":12466},"beta",{"type":415,"tag":783,"props":12468,"children":12469},{"style":790},[12470],{"type":420,"value":3385},{"type":415,"tag":783,"props":12472,"children":12473},{"class":785,"line":1061},[12474],{"type":415,"tag":783,"props":12475,"children":12476},{"style":790},[12477],{"type":420,"value":12346},{"type":415,"tag":783,"props":12479,"children":12480},{"class":785,"line":1099},[12481],{"type":415,"tag":783,"props":12482,"children":12483},{"style":790},[12484],{"type":420,"value":1272},{"type":415,"tag":416,"props":12486,"children":12487},{},[12488,12490,12494,12495,12499,12501,12505,12507,12511,12513,12517,12519,12523,12524,12528],{"type":420,"value":12489},"I have defined 2 environments ",{"type":415,"tag":2746,"props":12491,"children":12492},{},[12493],{"type":420,"value":12358},{"type":420,"value":3487},{"type":415,"tag":2746,"props":12496,"children":12497},{},[12498],{"type":420,"value":12426},{"type":420,"value":12500}," with a specific value for the ",{"type":415,"tag":2746,"props":12502,"children":12503},{},[12504],{"type":420,"value":12382},{"type":420,"value":12506}," variable. These environments share the ",{"type":415,"tag":2746,"props":12508,"children":12509},{},[12510],{"type":420,"value":12313},{"type":420,"value":12512}," as a common variable which is contained in the shared environment ",{"type":415,"tag":2746,"props":12514,"children":12515},{},[12516],{"type":420,"value":12289},{"type":420,"value":12518},". From my request file, I can now switch between environments and use the variables ",{"type":415,"tag":2746,"props":12520,"children":12521},{},[12522],{"type":420,"value":12382},{"type":420,"value":3487},{"type":415,"tag":2746,"props":12525,"children":12526},{},[12527],{"type":420,"value":12313},{"type":420,"value":12529}," to request the Microsoft Graph API.",{"type":415,"tag":416,"props":12531,"children":12532},{},[12533],{"type":415,"tag":1322,"props":12534,"children":12538},{"alt":12535,"className":12536,"src":12537},"Environment selection in vscode.",[1326,1327],"/posts/images/restclient_msgraph_1.png",[],{"type":415,"tag":443,"props":12540,"children":12542},{"id":12541},"what-i-like-about-rest-client",[12543],{"type":420,"value":12544},"What I like about REST Client?",{"type":415,"tag":416,"props":12546,"children":12547},{},[12548],{"type":420,"value":12549},"REST Client is a nice alternative to Postman as it allows to easily write requests and query APIs from Visual Studio Code. I am already a vscode user so I appreciate staying in the same environment I know and like for testing an API. REST Client may not offer as many functionalities as Postman but for the usage I have, it is quite enough.",{"type":415,"tag":416,"props":12551,"children":12552},{},[12553],{"type":420,"value":12554},"What I like about this tool is that you treat your requests as code: you can commit the files containing your requests, keep track of their modifications and share the requests with your colleagues in your Git project repository.",{"type":415,"tag":416,"props":12556,"children":12557},{},[12558],{"type":420,"value":12559},"I have seen quite a few people using it recently in video tutorials and if you have a look at the number of downloads it seems I am not the only one to find it useful.",{"type":415,"tag":443,"props":12561,"children":12563},{"id":12562},"getting-started",[12564],{"type":420,"value":12565},"Getting started",{"type":415,"tag":416,"props":12567,"children":12568},{},[12569],{"type":420,"value":12570},"There are a lot of other features in REST Client that I didn't talk about (generate code snippet, request history ...) so don't hesitate to give it a try. You can use it on any API you like (there is even basic support for SOAP) the same way you would use Postman or other tools.",{"type":415,"tag":416,"props":12572,"children":12573},{},[12574,12576,12581],{"type":420,"value":12575},"If you quickly want to convert some of your Postman queries, there is a ",{"type":415,"tag":2746,"props":12577,"children":12578},{},[12579],{"type":420,"value":12580},"Code",{"type":420,"value":12582}," button in Postman that allows you to see the HTTP request code that you can just copy and paste in vscode to use it with REST Client.",{"type":415,"tag":416,"props":12584,"children":12585},{},[12586],{"type":420,"value":12587},"Or if you prefer you can get started by testing the Star Wars API, you will find below the requests I used. Enjoy :)",{"type":415,"tag":773,"props":12589,"children":12592},{"className":1345,"code":12590,"filename":12591,"language":212,"meta":401,"style":401},"#### List of start wars planets\nGET https://swapi.co/api/planets/ HTTP/1.1\n\n### Get Luke Skywalker\n# @name lukeRequest\nGET https://swapi.co/api/people/?search=Luke HTTP/1.1\n\n### Get Luke Skywalker home planet\nGET {{lukeRequest.response.body.results[0].homeworld}} HTTP/1.1\n\n### List available resources\nGET https://swapi.co/api/ HTTP/1.1\n\n### Search planet with a specific name \n@planetName = Naboo\nGET https://swapi.co/api/planets/?search={{planetName}} HTTP/1.1\n\n### Search planet with a specific name in wookie encoding\nGET https://swapi.co/api/planets/?search={{planetName}}&format=wookiee HTTP/1.1\n\n### List starships\nGET https://swapi.co/api/starships HTTP/1.1\n","swapi.http",[12593],{"type":415,"tag":550,"props":12594,"children":12595},{"__ignoreMap":401},[12596,12604,12628,12635,12642,12666,12689,12696,12704,12728,12735,12743,12767,12774,12782,12799,12823,12830,12838,12862,12869,12877],{"type":415,"tag":783,"props":12597,"children":12598},{"class":785,"line":786},[12599],{"type":415,"tag":783,"props":12600,"children":12601},{"style":3340},[12602],{"type":420,"value":12603},"#### List of start wars planets\n",{"type":415,"tag":783,"props":12605,"children":12606},{"class":785,"line":796},[12607,12611,12616,12620,12624],{"type":415,"tag":783,"props":12608,"children":12609},{"style":1356},[12610],{"type":420,"value":1359},{"type":415,"tag":783,"props":12612,"children":12613},{"style":1362},[12614],{"type":420,"value":12615}," https://swapi.co/api/planets/ ",{"type":415,"tag":783,"props":12617,"children":12618},{"style":902},[12619],{"type":420,"value":213},{"type":415,"tag":783,"props":12621,"children":12622},{"style":1362},[12623],{"type":420,"value":3417},{"type":415,"tag":783,"props":12625,"children":12626},{"style":902},[12627],{"type":420,"value":4757},{"type":415,"tag":783,"props":12629,"children":12630},{"class":785,"line":840},[12631],{"type":415,"tag":783,"props":12632,"children":12633},{"emptyLinePlaceholder":5750},[12634],{"type":420,"value":5753},{"type":415,"tag":783,"props":12636,"children":12637},{"class":785,"line":866},[12638],{"type":415,"tag":783,"props":12639,"children":12640},{"style":3340},[12641],{"type":420,"value":4732},{"type":415,"tag":783,"props":12643,"children":12644},{"class":785,"line":893},[12645,12650,12655,12660],{"type":415,"tag":783,"props":12646,"children":12647},{"style":3340},[12648],{"type":420,"value":12649},"# ",{"type":415,"tag":783,"props":12651,"children":12652},{"style":1356},[12653],{"type":420,"value":12654},"@",{"type":415,"tag":783,"props":12656,"children":12658},{"style":12657},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA;--shiki-light-font-style:italic;--shiki-default-font-style:italic;--shiki-dark-font-style:italic",[12659],{"type":420,"value":8738},{"type":415,"tag":783,"props":12661,"children":12663},{"style":12662},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B;--shiki-light-font-style:italic;--shiki-default-font-style:italic;--shiki-dark-font-style:italic",[12664],{"type":420,"value":12665}," lukeRequest\n",{"type":415,"tag":783,"props":12667,"children":12668},{"class":785,"line":920},[12669,12673,12677,12681,12685],{"type":415,"tag":783,"props":12670,"children":12671},{"style":1356},[12672],{"type":420,"value":1359},{"type":415,"tag":783,"props":12674,"children":12675},{"style":1362},[12676],{"type":420,"value":4744},{"type":415,"tag":783,"props":12678,"children":12679},{"style":902},[12680],{"type":420,"value":213},{"type":415,"tag":783,"props":12682,"children":12683},{"style":1362},[12684],{"type":420,"value":3417},{"type":415,"tag":783,"props":12686,"children":12687},{"style":902},[12688],{"type":420,"value":4757},{"type":415,"tag":783,"props":12690,"children":12691},{"class":785,"line":959},[12692],{"type":415,"tag":783,"props":12693,"children":12694},{"emptyLinePlaceholder":5750},[12695],{"type":420,"value":5753},{"type":415,"tag":783,"props":12697,"children":12698},{"class":785,"line":997},[12699],{"type":415,"tag":783,"props":12700,"children":12701},{"style":3340},[12702],{"type":420,"value":12703},"### Get Luke Skywalker home planet\n",{"type":415,"tag":783,"props":12705,"children":12706},{"class":785,"line":1023},[12707,12711,12716,12720,12724],{"type":415,"tag":783,"props":12708,"children":12709},{"style":1356},[12710],{"type":420,"value":1359},{"type":415,"tag":783,"props":12712,"children":12713},{"style":1362},[12714],{"type":420,"value":12715}," {{lukeRequest.response.body.results[0].homeworld}} ",{"type":415,"tag":783,"props":12717,"children":12718},{"style":902},[12719],{"type":420,"value":213},{"type":415,"tag":783,"props":12721,"children":12722},{"style":1362},[12723],{"type":420,"value":3417},{"type":415,"tag":783,"props":12725,"children":12726},{"style":902},[12727],{"type":420,"value":4757},{"type":415,"tag":783,"props":12729,"children":12730},{"class":785,"line":1061},[12731],{"type":415,"tag":783,"props":12732,"children":12733},{"emptyLinePlaceholder":5750},[12734],{"type":420,"value":5753},{"type":415,"tag":783,"props":12736,"children":12737},{"class":785,"line":1099},[12738],{"type":415,"tag":783,"props":12739,"children":12740},{"style":3340},[12741],{"type":420,"value":12742},"### List available resources\n",{"type":415,"tag":783,"props":12744,"children":12745},{"class":785,"line":1137},[12746,12750,12755,12759,12763],{"type":415,"tag":783,"props":12747,"children":12748},{"style":1356},[12749],{"type":420,"value":1359},{"type":415,"tag":783,"props":12751,"children":12752},{"style":1362},[12753],{"type":420,"value":12754}," https://swapi.co/api/ ",{"type":415,"tag":783,"props":12756,"children":12757},{"style":902},[12758],{"type":420,"value":213},{"type":415,"tag":783,"props":12760,"children":12761},{"style":1362},[12762],{"type":420,"value":3417},{"type":415,"tag":783,"props":12764,"children":12765},{"style":902},[12766],{"type":420,"value":4757},{"type":415,"tag":783,"props":12768,"children":12769},{"class":785,"line":1175},[12770],{"type":415,"tag":783,"props":12771,"children":12772},{"emptyLinePlaceholder":5750},[12773],{"type":420,"value":5753},{"type":415,"tag":783,"props":12775,"children":12776},{"class":785,"line":1213},[12777],{"type":415,"tag":783,"props":12778,"children":12779},{"style":3340},[12780],{"type":420,"value":12781},"### Search planet with a specific name \n",{"type":415,"tag":783,"props":12783,"children":12784},{"class":785,"line":1239},[12785,12789,12794],{"type":415,"tag":783,"props":12786,"children":12787},{"style":902},[12788],{"type":420,"value":12654},{"type":415,"tag":783,"props":12790,"children":12791},{"style":1362},[12792],{"type":420,"value":12793},"planetName = ",{"type":415,"tag":783,"props":12795,"children":12796},{"style":825},[12797],{"type":420,"value":12798},"Naboo\n",{"type":415,"tag":783,"props":12800,"children":12801},{"class":785,"line":1248},[12802,12806,12811,12815,12819],{"type":415,"tag":783,"props":12803,"children":12804},{"style":1356},[12805],{"type":420,"value":1359},{"type":415,"tag":783,"props":12807,"children":12808},{"style":1362},[12809],{"type":420,"value":12810}," https://swapi.co/api/planets/?search={{planetName}} ",{"type":415,"tag":783,"props":12812,"children":12813},{"style":902},[12814],{"type":420,"value":213},{"type":415,"tag":783,"props":12816,"children":12817},{"style":1362},[12818],{"type":420,"value":3417},{"type":415,"tag":783,"props":12820,"children":12821},{"style":902},[12822],{"type":420,"value":4757},{"type":415,"tag":783,"props":12824,"children":12825},{"class":785,"line":1257},[12826],{"type":415,"tag":783,"props":12827,"children":12828},{"emptyLinePlaceholder":5750},[12829],{"type":420,"value":5753},{"type":415,"tag":783,"props":12831,"children":12832},{"class":785,"line":1266},[12833],{"type":415,"tag":783,"props":12834,"children":12835},{"style":3340},[12836],{"type":420,"value":12837},"### Search planet with a specific name in wookie encoding\n",{"type":415,"tag":783,"props":12839,"children":12840},{"class":785,"line":6616},[12841,12845,12850,12854,12858],{"type":415,"tag":783,"props":12842,"children":12843},{"style":1356},[12844],{"type":420,"value":1359},{"type":415,"tag":783,"props":12846,"children":12847},{"style":1362},[12848],{"type":420,"value":12849}," https://swapi.co/api/planets/?search={{planetName}}&format=wookiee ",{"type":415,"tag":783,"props":12851,"children":12852},{"style":902},[12853],{"type":420,"value":213},{"type":415,"tag":783,"props":12855,"children":12856},{"style":1362},[12857],{"type":420,"value":3417},{"type":415,"tag":783,"props":12859,"children":12860},{"style":902},[12861],{"type":420,"value":4757},{"type":415,"tag":783,"props":12863,"children":12864},{"class":785,"line":6648},[12865],{"type":415,"tag":783,"props":12866,"children":12867},{"emptyLinePlaceholder":5750},[12868],{"type":420,"value":5753},{"type":415,"tag":783,"props":12870,"children":12871},{"class":785,"line":10484},[12872],{"type":415,"tag":783,"props":12873,"children":12874},{"style":3340},[12875],{"type":420,"value":12876},"### List starships\n",{"type":415,"tag":783,"props":12878,"children":12879},{"class":785,"line":10517},[12880,12884,12889,12893,12897],{"type":415,"tag":783,"props":12881,"children":12882},{"style":1356},[12883],{"type":420,"value":1359},{"type":415,"tag":783,"props":12885,"children":12886},{"style":1362},[12887],{"type":420,"value":12888}," https://swapi.co/api/starships ",{"type":415,"tag":783,"props":12890,"children":12891},{"style":902},[12892],{"type":420,"value":213},{"type":415,"tag":783,"props":12894,"children":12895},{"style":1362},[12896],{"type":420,"value":3417},{"type":415,"tag":783,"props":12898,"children":12899},{"style":902},[12900],{"type":420,"value":4757},{"type":415,"tag":1420,"props":12902,"children":12903},{},[12904],{"type":420,"value":1424},{"title":401,"searchDepth":796,"depth":796,"links":12906},[12907,12908,12909,12910,12911],{"id":12056,"depth":796,"text":12059},{"id":12092,"depth":796,"text":12095},{"id":12195,"depth":796,"text":12198},{"id":12541,"depth":796,"text":12544},{"id":12562,"depth":796,"text":12565},"content:1.posts:1.testing-your-api-with-rest-client.md","1.posts/1.testing-your-api-with-rest-client.md",1716749600574]