[{"data":1,"prerenderedAt":3430},["Reactive",2],{"navigation":3,"aAII9Cz3yR":204,"tags-nushell":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,1485,2842],{"_path":124,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":123,"description":402,"lead":403,"date":404,"image":405,"badge":407,"tags":409,"body":410,"_type":1480,"_id":1481,"_source":1482,"_file":1483,"_extension":1484},"posts",false,"","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":406},"/images/padlock_1.jpg",{"label":408},"Tooling",[206,213,243,225,260],{"type":411,"children":412,"toc":1474},"root",[413,458,465,502,554,605,617,622,645,651,656,665,670,676,697,710,719,731,916,921,930,935,944,957,966,982,994,1003,1023,1032,1045,1054,1083,1088,1097,1110,1118,1123,1132,1144,1153,1165,1170,1438,1444,1449,1463,1468],{"type":414,"tag":415,"props":416,"children":417},"element","p",{},[418,421,430,432,439,441,447,449,456],{"type":419,"value":420},"text","When using some API Clients (like ",{"type":414,"tag":422,"props":423,"children":427},"a",{"href":424,"rel":425},"https://marketplace.visualstudio.com/items?itemName=humao.rest-client",[426],"nofollow",[428],{"type":419,"value":429},"REST Client",{"type":419,"value":431}," or the ",{"type":414,"tag":422,"props":433,"children":436},{"href":434,"rel":435},"https://www.jetbrains.com/help/rider/Http_client_in__product__code_editor.html",[426],[437],{"type":419,"value":438},"HTTP Client of JetBrains' IDEs",{"type":419,"value":440},"), 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":414,"tag":422,"props":442,"children":445},{"href":443,"rel":444},"https://docs.microsoft.com/en-us/cli/azure/",[426],[446],{"type":419,"value":225},{"type":419,"value":448}," and ",{"type":414,"tag":422,"props":450,"children":453},{"href":451,"rel":452},"https://www.nushell.sh/",[426],[454],{"type":419,"value":455},"Nushell",{"type":419,"value":457},".",{"type":414,"tag":459,"props":460,"children":462},"h2",{"id":461},"the-problem-keep-secrets-secure-while-making-http-requests",[463],{"type":419,"value":464},"The problem: keep secrets secure while making HTTP requests",{"type":414,"tag":415,"props":466,"children":467},{},[468,470,476,478,484,486,492,494,500],{"type":419,"value":469},"If you have read my article \"",{"type":414,"tag":422,"props":471,"children":474},{"href":472,"rel":473},"https://www.techwatching.dev/posts/testing-your-api-with-rest-client",[426],[475],{"type":419,"value":9},{"type":419,"value":477},"\", you know I am a big fan of using the vscode extension ",{"type":414,"tag":479,"props":480,"children":482},"code",{"className":481},[],[483],{"type":419,"value":429},{"type":419,"value":485}," 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":414,"tag":479,"props":487,"children":489},{"className":488},[],[490],{"type":419,"value":491},".http",{"type":419,"value":493}," or ",{"type":414,"tag":479,"props":495,"children":497},{"className":496},[],[498],{"type":419,"value":499},".rest",{"type":419,"value":501}," 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":414,"tag":503,"props":504,"children":507},"pre",{"className":505,"code":506,"language":212,"meta":401,"style":401},"language-http shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","### Get Luke Skywalker\nGET https://swapi.co/api/people/?search=Luke HTTP/1.1\n",[508],{"type":414,"tag":479,"props":509,"children":510},{"__ignoreMap":401},[511,523],{"type":414,"tag":512,"props":513,"children":516},"span",{"class":514,"line":515},"line",1,[517],{"type":414,"tag":512,"props":518,"children":520},{"style":519},"--shiki-light:#90A4AE;--shiki-default:#546E7A;--shiki-dark:#676E95;--shiki-light-font-style:italic;--shiki-default-font-style:italic;--shiki-dark-font-style:italic",[521],{"type":419,"value":522},"### Get Luke Skywalker\n",{"type":414,"tag":512,"props":524,"children":526},{"class":514,"line":525},2,[527,533,539,544,549],{"type":414,"tag":512,"props":528,"children":530},{"style":529},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF;--shiki-light-font-style:italic;--shiki-default-font-style:italic;--shiki-dark-font-style:italic",[531],{"type":419,"value":532},"GET",{"type":414,"tag":512,"props":534,"children":536},{"style":535},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[537],{"type":419,"value":538}," https://swapi.co/api/people/?search=Luke ",{"type":414,"tag":512,"props":540,"children":542},{"style":541},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[543],{"type":419,"value":213},{"type":414,"tag":512,"props":545,"children":546},{"style":535},[547],{"type":419,"value":548},"/",{"type":414,"tag":512,"props":550,"children":551},{"style":541},[552],{"type":419,"value":553},"1.1\n",{"type":414,"tag":415,"props":555,"children":556},{},[557,559,566,568,574,576,581,582,587,589,595,597,603],{"type":419,"value":558},"I have been using recently the IDE ",{"type":414,"tag":422,"props":560,"children":563},{"href":561,"rel":562},"https://www.jetbrains.com/fr-fr/rider/",[426],[564],{"type":419,"value":565},"Rider",{"type":419,"value":567},", which has (like all the other JetBrains' IDEs) an integrated ",{"type":414,"tag":422,"props":569,"children":571},{"href":434,"rel":570},[426],[572],{"type":419,"value":573},"HTTP Client",{"type":419,"value":575},". It's very similar to REST Client (same syntax for the requests that are written in ",{"type":414,"tag":479,"props":577,"children":579},{"className":578},[],[580],{"type":419,"value":491},{"type":419,"value":493},{"type":414,"tag":479,"props":583,"children":585},{"className":584},[],[586],{"type":419,"value":499},{"type":419,"value":588}," files) with some extra features. With this HTTP Client, environment variables are stored in a public JSON environment file ",{"type":414,"tag":479,"props":590,"children":592},{"className":591},[],[593],{"type":419,"value":594},"http-client.env.json",{"type":419,"value":596}," that can be committed. However, secrets can be stored in a private JSON environment file ",{"type":414,"tag":479,"props":598,"children":600},{"className":599},[],[601],{"type":419,"value":602},"http-client.private.env.json",{"type":419,"value":604}," 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":414,"tag":415,"props":606,"children":607},{},[608],{"type":414,"tag":609,"props":610,"children":616},"img",{"alt":611,"className":612,"src":615},"HTTP file, HTTP environment file, and HTTP private environment opnened file in Rider.",[613,614],"rounded-lg","mx-auto","/posts/images/httpclientssecrets_rider_1.png",[],{"type":414,"tag":415,"props":618,"children":619},{},[620],{"type":419,"value":621},"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":414,"tag":623,"props":624,"children":626},"callout",{"icon":625},"i-heroicons-chat-bubble-left-20-solid",[627],{"type":414,"tag":415,"props":628,"children":629},{},[630,632,637,638,643],{"type":419,"value":631},"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":414,"tag":479,"props":633,"children":635},{"className":634},[],[636],{"type":419,"value":429},{"type":419,"value":493},{"type":414,"tag":479,"props":639,"children":641},{"className":640},[],[642],{"type":419,"value":573},{"type":419,"value":644}," from JetBrains was managing secrets.",{"type":414,"tag":459,"props":646,"children":648},{"id":647},"the-solution-use-azure-key-vault-and-scripting",[649],{"type":419,"value":650},"The solution: use Azure Key Vault and scripting",{"type":414,"tag":415,"props":652,"children":653},{},[654],{"type":419,"value":655},"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":414,"tag":623,"props":657,"children":659},{"icon":658},"i-heroicons-light-bulb",[660],{"type":414,"tag":415,"props":661,"children":662},{},[663],{"type":419,"value":664},"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":414,"tag":415,"props":666,"children":667},{},[668],{"type":419,"value":669},"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":414,"tag":459,"props":671,"children":673},{"id":672},"lets-script-that-with-azure-cli-and-nushell",[674],{"type":419,"value":675},"Let's script that with Azure CLI and Nushell!",{"type":414,"tag":415,"props":677,"children":678},{},[679,681,687,689,695],{"type":419,"value":680},"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":414,"tag":422,"props":682,"children":685},{"href":683,"rel":684},"https://www.techwatching.dev/posts/welcome-azure-cli",[426],[686],{"type":419,"value":15},{"type":419,"value":688},"\". If you don't know Nushell you can check its ",{"type":414,"tag":422,"props":690,"children":692},{"href":451,"rel":691},[426],[693],{"type":419,"value":694},"website",{"type":419,"value":696}," or just continue reading this article to see how nice this shell is.",{"type":414,"tag":415,"props":698,"children":699},{},[700,702,708],{"type":419,"value":701},"I have already created an Azure Key Vault named ",{"type":414,"tag":479,"props":703,"children":705},{"className":704},[],[706],{"type":419,"value":707},"httpclient-vault",{"type":419,"value":709}," and set 3 secrets in it.",{"type":414,"tag":415,"props":711,"children":712},{},[713],{"type":414,"tag":609,"props":714,"children":718},{"alt":715,"className":716,"src":717},"The Secrets view of an Azure Key Vault resource in Azure Portal",[613,614],"/posts/images/httpclientssecrets_keyvault_1.png",[],{"type":414,"tag":415,"props":720,"children":721},{},[722,724,729],{"type":419,"value":723},"What I am trying to achieve is to produce the following file ",{"type":414,"tag":479,"props":725,"children":727},{"className":726},[],[728],{"type":419,"value":602},{"type":419,"value":730},":",{"type":414,"tag":503,"props":732,"children":736},{"className":733,"code":734,"language":735,"meta":401,"style":401},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"development\":\n  {\n    \"ApiKey\": \"12345678\",\n    \"Username\": \"admin\",\n    \"UserPassword\": \"Password\"\n  }\n}\n","json",[737],{"type":414,"tag":479,"props":738,"children":739},{"__ignoreMap":401},[740,749,773,782,825,863,898,907],{"type":414,"tag":512,"props":741,"children":742},{"class":514,"line":515},[743],{"type":414,"tag":512,"props":744,"children":746},{"style":745},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[747],{"type":419,"value":748},"{\n",{"type":414,"tag":512,"props":750,"children":751},{"class":514,"line":525},[752,757,763,768],{"type":414,"tag":512,"props":753,"children":754},{"style":745},[755],{"type":419,"value":756},"  \"",{"type":414,"tag":512,"props":758,"children":760},{"style":759},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[761],{"type":419,"value":762},"development",{"type":414,"tag":512,"props":764,"children":765},{"style":745},[766],{"type":419,"value":767},"\"",{"type":414,"tag":512,"props":769,"children":770},{"style":745},[771],{"type":419,"value":772},":\n",{"type":414,"tag":512,"props":774,"children":776},{"class":514,"line":775},3,[777],{"type":414,"tag":512,"props":778,"children":779},{"style":745},[780],{"type":419,"value":781},"  {\n",{"type":414,"tag":512,"props":783,"children":785},{"class":514,"line":784},4,[786,791,797,801,805,810,816,820],{"type":414,"tag":512,"props":787,"children":788},{"style":745},[789],{"type":419,"value":790},"    \"",{"type":414,"tag":512,"props":792,"children":794},{"style":793},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[795],{"type":419,"value":796},"ApiKey",{"type":414,"tag":512,"props":798,"children":799},{"style":745},[800],{"type":419,"value":767},{"type":414,"tag":512,"props":802,"children":803},{"style":745},[804],{"type":419,"value":730},{"type":414,"tag":512,"props":806,"children":807},{"style":745},[808],{"type":419,"value":809}," \"",{"type":414,"tag":512,"props":811,"children":813},{"style":812},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[814],{"type":419,"value":815},"12345678",{"type":414,"tag":512,"props":817,"children":818},{"style":745},[819],{"type":419,"value":767},{"type":414,"tag":512,"props":821,"children":822},{"style":745},[823],{"type":419,"value":824},",\n",{"type":414,"tag":512,"props":826,"children":828},{"class":514,"line":827},5,[829,833,838,842,846,850,855,859],{"type":414,"tag":512,"props":830,"children":831},{"style":745},[832],{"type":419,"value":790},{"type":414,"tag":512,"props":834,"children":835},{"style":793},[836],{"type":419,"value":837},"Username",{"type":414,"tag":512,"props":839,"children":840},{"style":745},[841],{"type":419,"value":767},{"type":414,"tag":512,"props":843,"children":844},{"style":745},[845],{"type":419,"value":730},{"type":414,"tag":512,"props":847,"children":848},{"style":745},[849],{"type":419,"value":809},{"type":414,"tag":512,"props":851,"children":852},{"style":812},[853],{"type":419,"value":854},"admin",{"type":414,"tag":512,"props":856,"children":857},{"style":745},[858],{"type":419,"value":767},{"type":414,"tag":512,"props":860,"children":861},{"style":745},[862],{"type":419,"value":824},{"type":414,"tag":512,"props":864,"children":866},{"class":514,"line":865},6,[867,871,876,880,884,888,893],{"type":414,"tag":512,"props":868,"children":869},{"style":745},[870],{"type":419,"value":790},{"type":414,"tag":512,"props":872,"children":873},{"style":793},[874],{"type":419,"value":875},"UserPassword",{"type":414,"tag":512,"props":877,"children":878},{"style":745},[879],{"type":419,"value":767},{"type":414,"tag":512,"props":881,"children":882},{"style":745},[883],{"type":419,"value":730},{"type":414,"tag":512,"props":885,"children":886},{"style":745},[887],{"type":419,"value":809},{"type":414,"tag":512,"props":889,"children":890},{"style":812},[891],{"type":419,"value":892},"Password",{"type":414,"tag":512,"props":894,"children":895},{"style":745},[896],{"type":419,"value":897},"\"\n",{"type":414,"tag":512,"props":899,"children":901},{"class":514,"line":900},7,[902],{"type":414,"tag":512,"props":903,"children":904},{"style":745},[905],{"type":419,"value":906},"  }\n",{"type":414,"tag":512,"props":908,"children":910},{"class":514,"line":909},8,[911],{"type":414,"tag":512,"props":912,"children":913},{"style":745},[914],{"type":419,"value":915},"}\n",{"type":414,"tag":415,"props":917,"children":918},{},[919],{"type":419,"value":920},"First, let's list the secrets in the Key Vault:",{"type":414,"tag":415,"props":922,"children":923},{},[924],{"type":414,"tag":609,"props":925,"children":929},{"alt":926,"className":927,"src":928},"An Azure CLI command that lists Key Vault secrets in terminal.",[613,614],"/posts/images/httpclientssecrets_script_1.png",[],{"type":414,"tag":415,"props":931,"children":932},{},[933],{"type":419,"value":934},"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":414,"tag":415,"props":936,"children":937},{},[938],{"type":414,"tag":609,"props":939,"children":943},{"alt":940,"className":941,"src":942},"An Azure CLI command using JMESPath that lists Key Vault secrets in terminal.",[613,614],"/posts/images/httpclientssecrets_script_2.png",[],{"type":414,"tag":415,"props":945,"children":946},{},[947,949,955],{"type":419,"value":948},"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":414,"tag":479,"props":950,"children":952},{"className":951},[],[953],{"type":419,"value":954},"from json",{"type":419,"value":956}," command.",{"type":414,"tag":415,"props":958,"children":959},{},[960],{"type":414,"tag":609,"props":961,"children":965},{"alt":962,"className":963,"src":964},"The output of the \"az keyvault secret list --vault-name httpclient-vault | from json\" command in terminal.",[613,614],"/posts/images/httpclientssecrets_script_3.png",[],{"type":414,"tag":623,"props":967,"children":968},{"icon":658},[969],{"type":414,"tag":415,"props":970,"children":971},{},[972,974,980],{"type":419,"value":973},"Nu has many ",{"type":414,"tag":479,"props":975,"children":977},{"className":976},[],[978],{"type":419,"value":979},"from",{"type":419,"value":981}," commands to convert data from different formats to structured data/table.",{"type":414,"tag":415,"props":983,"children":984},{},[985,987,993],{"type":419,"value":986},"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":414,"tag":479,"props":988,"children":990},{"className":989},[],[991],{"type":419,"value":992},"az keyvault secret show --id $secretId",{"type":419,"value":457},{"type":414,"tag":415,"props":995,"children":996},{},[997],{"type":414,"tag":609,"props":998,"children":1002},{"alt":999,"className":1000,"src":1001},"An Azure CLI command that get a secret from Key Vault in terminal.",[613,614],"/posts/images/httpclientssecrets_script_4.png",[],{"type":414,"tag":415,"props":1004,"children":1005},{},[1006,1008,1013,1015,1021],{"type":419,"value":1007},"Again we can use the ",{"type":414,"tag":479,"props":1009,"children":1011},{"className":1010},[],[1012],{"type":419,"value":954},{"type":419,"value":1014}," command, and the ",{"type":414,"tag":479,"props":1016,"children":1018},{"className":1017},[],[1019],{"type":419,"value":1020},"get",{"type":419,"value":1022}," command to only retrieve the value of a secret.",{"type":414,"tag":415,"props":1024,"children":1025},{},[1026],{"type":414,"tag":609,"props":1027,"children":1031},{"alt":1028,"className":1029,"src":1030},"The output of the nushell script retrieving a secret value from keyvault.",[613,614],"/posts/images/httpclientssecrets_script_5.png",[],{"type":414,"tag":415,"props":1033,"children":1034},{},[1035,1037,1043],{"type":419,"value":1036},"Now that we know how to retrieve the value of a secret, we can insert a new column ",{"type":414,"tag":479,"props":1038,"children":1040},{"className":1039},[],[1041],{"type":419,"value":1042},"value",{"type":419,"value":1044}," into our table that will be filled with the value of each secret retrieved   using the previous command:",{"type":414,"tag":415,"props":1046,"children":1047},{},[1048],{"type":414,"tag":609,"props":1049,"children":1053},{"alt":1050,"className":1051,"src":1052},"The output of the nushell script retrieving a list of secrets from keyvault.",[613,614],"/posts/images/httpclientssecrets_script_6.png",[],{"type":414,"tag":415,"props":1055,"children":1056},{},[1057,1059,1065,1067,1073,1075,1081],{"type":419,"value":1058},"The ",{"type":414,"tag":479,"props":1060,"children":1062},{"className":1061},[],[1063],{"type":419,"value":1064},"{|secret| (az keyvault secret show --id $secret.id | from json | get value)}",{"type":419,"value":1066}," part is a block that is executed for each row. The ",{"type":414,"tag":479,"props":1068,"children":1070},{"className":1069},[],[1071],{"type":419,"value":1072},"secret",{"type":419,"value":1074}," 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":414,"tag":479,"props":1076,"children":1078},{"className":1077},[],[1079],{"type":419,"value":1080},"$secret",{"type":419,"value":1082},". 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":414,"tag":415,"props":1084,"children":1085},{},[1086],{"type":419,"value":1087},"As we are only interested in the columns \"name\" and \"value\", we only select them.",{"type":414,"tag":415,"props":1089,"children":1090},{},[1091],{"type":414,"tag":609,"props":1092,"children":1096},{"alt":1093,"className":1094,"src":1095},"The output of the nushell script retrieving Azure Key Vault secrets (name and value).",[613,614],"/posts/images/httpclientssecrets_script_7.png",[],{"type":414,"tag":415,"props":1098,"children":1099},{},[1100,1102,1108],{"type":419,"value":1101},"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":414,"tag":479,"props":1103,"children":1105},{"className":1104},[],[1106],{"type":419,"value":1107},"transpose",{"type":419,"value":1109}," with the proper flags to do that:",{"type":414,"tag":415,"props":1111,"children":1112},{},[1113],{"type":414,"tag":609,"props":1114,"children":1117},{"alt":1093,"className":1115,"src":1116},[613,614],"/posts/images/httpclientssecrets_script_8.png",[],{"type":414,"tag":415,"props":1119,"children":1120},{},[1121],{"type":419,"value":1122},"Then we wrap the key-value pairs in a JSON object corresponding to the development environment:",{"type":414,"tag":415,"props":1124,"children":1125},{},[1126],{"type":414,"tag":609,"props":1127,"children":1131},{"alt":1128,"className":1129,"src":1130},"The output of the nushell script creating a JSON object from Azure Key Vault secrets.",[613,614],"/posts/images/httpclientssecrets_script_9.png",[],{"type":414,"tag":415,"props":1133,"children":1134},{},[1135,1137,1143],{"type":419,"value":1136},"We can check we get the JSON we want with the ",{"type":414,"tag":479,"props":1138,"children":1140},{"className":1139},[],[1141],{"type":419,"value":1142},"to json",{"type":419,"value":956},{"type":414,"tag":415,"props":1145,"children":1146},{},[1147],{"type":414,"tag":609,"props":1148,"children":1152},{"alt":1149,"className":1150,"src":1151},"The output of the nushell script creating a JSON string from Azure Key Vault secrets.",[613,614],"/posts/images/httpclientssecrets_script_10.png",[],{"type":414,"tag":415,"props":1154,"children":1155},{},[1156,1158,1163],{"type":419,"value":1157},"And finally, we can save the data in a ",{"type":414,"tag":479,"props":1159,"children":1161},{"className":1160},[],[1162],{"type":419,"value":602},{"type":419,"value":1164}," file using the save command.",{"type":414,"tag":415,"props":1166,"children":1167},{},[1168],{"type":419,"value":1169},"Here is the final script 🔽:",{"type":414,"tag":503,"props":1171,"children":1174},{"className":1172,"code":1173,"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",[1175],{"type":414,"tag":479,"props":1176,"children":1177},{"__ignoreMap":401},[1178,1186,1230,1242,1343,1364,1386,1416,1429],{"type":414,"tag":512,"props":1179,"children":1180},{"class":514,"line":515},[1181],{"type":414,"tag":512,"props":1182,"children":1183},{"style":535},[1184],{"type":419,"value":1185},"(\n",{"type":414,"tag":512,"props":1187,"children":1188},{"class":514,"line":525},[1189,1194,1199,1204,1209,1214,1220,1225],{"type":414,"tag":512,"props":1190,"children":1191},{"style":793},[1192],{"type":419,"value":1193},"  az",{"type":414,"tag":512,"props":1195,"children":1196},{"style":812},[1197],{"type":419,"value":1198}," keyvault",{"type":414,"tag":512,"props":1200,"children":1201},{"style":812},[1202],{"type":419,"value":1203}," secret",{"type":414,"tag":512,"props":1205,"children":1206},{"style":812},[1207],{"type":419,"value":1208}," list",{"type":414,"tag":512,"props":1210,"children":1211},{"style":529},[1212],{"type":419,"value":1213}," --",{"type":414,"tag":512,"props":1215,"children":1217},{"style":1216},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8;--shiki-light-font-style:italic;--shiki-default-font-style:italic;--shiki-dark-font-style:italic",[1218],{"type":419,"value":1219},"vault-name",{"type":414,"tag":512,"props":1221,"children":1222},{"style":812},[1223],{"type":419,"value":1224}," httpclient-vault",{"type":414,"tag":512,"props":1226,"children":1227},{"style":529},[1228],{"type":419,"value":1229}," |\n",{"type":414,"tag":512,"props":1231,"children":1232},{"class":514,"line":775},[1233,1238],{"type":414,"tag":512,"props":1234,"children":1235},{"style":541},[1236],{"type":419,"value":1237},"  from json",{"type":414,"tag":512,"props":1239,"children":1240},{"style":529},[1241],{"type":419,"value":1229},{"type":414,"tag":512,"props":1243,"children":1244},{"class":514,"line":784},[1245,1250,1255,1260,1265,1269,1274,1279,1283,1287,1292,1296,1301,1306,1310,1315,1320,1325,1329,1334,1339],{"type":414,"tag":512,"props":1246,"children":1247},{"style":541},[1248],{"type":419,"value":1249},"  insert",{"type":414,"tag":512,"props":1251,"children":1252},{"style":812},[1253],{"type":419,"value":1254}," value",{"type":414,"tag":512,"props":1256,"children":1257},{"style":745},[1258],{"type":419,"value":1259}," {",{"type":414,"tag":512,"props":1261,"children":1262},{"style":535},[1263],{"type":419,"value":1264},"|",{"type":414,"tag":512,"props":1266,"children":1267},{"style":1216},[1268],{"type":419,"value":1072},{"type":414,"tag":512,"props":1270,"children":1271},{"style":535},[1272],{"type":419,"value":1273},"| (",{"type":414,"tag":512,"props":1275,"children":1276},{"style":793},[1277],{"type":419,"value":1278},"az",{"type":414,"tag":512,"props":1280,"children":1281},{"style":812},[1282],{"type":419,"value":1198},{"type":414,"tag":512,"props":1284,"children":1285},{"style":812},[1286],{"type":419,"value":1203},{"type":414,"tag":512,"props":1288,"children":1289},{"style":812},[1290],{"type":419,"value":1291}," show",{"type":414,"tag":512,"props":1293,"children":1294},{"style":529},[1295],{"type":419,"value":1213},{"type":414,"tag":512,"props":1297,"children":1298},{"style":1216},[1299],{"type":419,"value":1300},"id",{"type":414,"tag":512,"props":1302,"children":1303},{"style":535},[1304],{"type":419,"value":1305}," $secret.id ",{"type":414,"tag":512,"props":1307,"children":1308},{"style":529},[1309],{"type":419,"value":1264},{"type":414,"tag":512,"props":1311,"children":1312},{"style":541},[1313],{"type":419,"value":1314}," from json",{"type":414,"tag":512,"props":1316,"children":1317},{"style":529},[1318],{"type":419,"value":1319}," |",{"type":414,"tag":512,"props":1321,"children":1322},{"style":541},[1323],{"type":419,"value":1324}," get",{"type":414,"tag":512,"props":1326,"children":1327},{"style":812},[1328],{"type":419,"value":1254},{"type":414,"tag":512,"props":1330,"children":1331},{"style":535},[1332],{"type":419,"value":1333},")",{"type":414,"tag":512,"props":1335,"children":1336},{"style":745},[1337],{"type":419,"value":1338},"}",{"type":414,"tag":512,"props":1340,"children":1341},{"style":529},[1342],{"type":419,"value":1229},{"type":414,"tag":512,"props":1344,"children":1345},{"class":514,"line":827},[1346,1351,1356,1360],{"type":414,"tag":512,"props":1347,"children":1348},{"style":541},[1349],{"type":419,"value":1350},"  select",{"type":414,"tag":512,"props":1352,"children":1353},{"style":812},[1354],{"type":419,"value":1355}," name",{"type":414,"tag":512,"props":1357,"children":1358},{"style":812},[1359],{"type":419,"value":1254},{"type":414,"tag":512,"props":1361,"children":1362},{"style":529},[1363],{"type":419,"value":1229},{"type":414,"tag":512,"props":1365,"children":1366},{"class":514,"line":865},[1367,1372,1377,1382],{"type":414,"tag":512,"props":1368,"children":1369},{"style":541},[1370],{"type":419,"value":1371},"  transpose",{"type":414,"tag":512,"props":1373,"children":1374},{"style":529},[1375],{"type":419,"value":1376}," -",{"type":414,"tag":512,"props":1378,"children":1379},{"style":1216},[1380],{"type":419,"value":1381},"rd",{"type":414,"tag":512,"props":1383,"children":1384},{"style":529},[1385],{"type":419,"value":1229},{"type":414,"tag":512,"props":1387,"children":1388},{"class":514,"line":900},[1389,1394,1399,1403,1408,1412],{"type":414,"tag":512,"props":1390,"children":1391},{"style":745},[1392],{"type":419,"value":1393},"  {",{"type":414,"tag":512,"props":1395,"children":1396},{"style":535},[1397],{"type":419,"value":1398}," development",{"type":414,"tag":512,"props":1400,"children":1401},{"style":529},[1402],{"type":419,"value":730},{"type":414,"tag":512,"props":1404,"children":1405},{"style":535},[1406],{"type":419,"value":1407}," $in ",{"type":414,"tag":512,"props":1409,"children":1410},{"style":745},[1411],{"type":419,"value":1338},{"type":414,"tag":512,"props":1413,"children":1414},{"style":529},[1415],{"type":419,"value":1229},{"type":414,"tag":512,"props":1417,"children":1418},{"class":514,"line":909},[1419,1424],{"type":414,"tag":512,"props":1420,"children":1421},{"style":541},[1422],{"type":419,"value":1423},"  save",{"type":414,"tag":512,"props":1425,"children":1426},{"style":812},[1427],{"type":419,"value":1428}," http-client.private.env.json\n",{"type":414,"tag":512,"props":1430,"children":1432},{"class":514,"line":1431},9,[1433],{"type":414,"tag":512,"props":1434,"children":1435},{"style":535},[1436],{"type":419,"value":1437},")\n",{"type":414,"tag":459,"props":1439,"children":1441},{"id":1440},"final-thoughts",[1442],{"type":419,"value":1443},"Final thoughts",{"type":414,"tag":415,"props":1445,"children":1446},{},[1447],{"type":419,"value":1448},"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":414,"tag":415,"props":1450,"children":1451},{},[1452,1454,1461],{"type":419,"value":1453},"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":414,"tag":422,"props":1455,"children":1458},{"href":1456,"rel":1457},"https://www.doppler.com/",[426],[1459],{"type":419,"value":1460},"Doppler",{"type":419,"value":1462}," which seems nice) that can help you manage secrets securely.",{"type":414,"tag":415,"props":1464,"children":1465},{},[1466],{"type":419,"value":1467},"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":414,"tag":1469,"props":1470,"children":1471},"style",{},[1472],{"type":419,"value":1473},"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":525,"depth":525,"links":1475},[1476,1477,1478,1479],{"id":461,"depth":525,"text":464},{"id":647,"depth":525,"text":650},{"id":672,"depth":525,"text":675},{"id":1440,"depth":525,"text":1443},"markdown","content:1.posts:39.http-clients-secrets.md","content","1.posts/39.http-clients-secrets.md","md",{"_path":70,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":69,"description":1486,"lead":1487,"date":1488,"image":1489,"badge":1491,"tags":1493,"body":1494,"_type":1480,"_id":2840,"_source":1482,"_file":2841,"_extension":1484},"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":1490},"/images/surface_2.jpg",{"label":1492},"Development",[293,304,296,206,243,307],{"type":411,"children":1495,"toc":2818},[1496,1510,1516,1530,1539,1551,1556,1562,1569,1597,1627,1639,1652,1663,1676,1687,1692,1698,1712,1717,1723,1729,1738,1743,1748,1754,1764,1778,1787,1801,1810,1823,1829,1849,1867,1872,2021,2026,2039,2044,2112,2132,2229,2241,2300,2305,2453,2462,2481,2490,2495,2504,2510,2524,2753,2758,2767,2773,2778,2783,2814],{"type":414,"tag":415,"props":1497,"children":1498},{},[1499,1501,1508],{"type":419,"value":1500},"In my ",{"type":414,"tag":422,"props":1502,"children":1505},{"href":1503,"rel":1504},"https://www.techwatching.dev/tags/winget",[426],[1506],{"type":419,"value":1507},"previous articles about winget",{"type":419,"value":1509}," I talked about installing packages but I did not talk about producing packages for Windows Package Manager. So let's set things right.",{"type":414,"tag":459,"props":1511,"children":1513},{"id":1512},"about-winget-packages",[1514],{"type":419,"value":1515},"About winget packages",{"type":414,"tag":415,"props":1517,"children":1518},{},[1519,1521,1528],{"type":419,"value":1520},"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":414,"tag":422,"props":1522,"children":1525},{"href":1523,"rel":1524},"https://github.com/microsoft/winget-pkgs",[426],[1526],{"type":419,"value":1527},"Windows Package Manager Community Repository",{"type":419,"value":1529}," 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":414,"tag":415,"props":1531,"children":1532},{},[1533],{"type":414,"tag":609,"props":1534,"children":1538},{"alt":1535,"className":1536,"src":1537},"Windows Package Manager Community repository readme.",[613,614],"/posts/images/wingetcreate_package_repository.png",[],{"type":414,"tag":415,"props":1540,"children":1541},{},[1542,1544],{"type":419,"value":1543},"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":414,"tag":422,"props":1545,"children":1548},{"href":1546,"rel":1547},"https://docs.microsoft.com/en-us/windows/package-manager/package/",[426],[1549],{"type":419,"value":1550},"documentation",{"type":414,"tag":415,"props":1552,"children":1553},{},[1554],{"type":419,"value":1555},"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":414,"tag":459,"props":1557,"children":1559},{"id":1558},"wingetcreate-to-the-rescue",[1560],{"type":419,"value":1561},"WingetCreate to the rescue",{"type":414,"tag":1563,"props":1564,"children":1566},"h3",{"id":1565},"introducing-wingetcreate",[1567],{"type":419,"value":1568},"Introducing WingetCreate",{"type":414,"tag":415,"props":1570,"children":1571},{},[1572,1579,1581,1588,1590,1596],{"type":414,"tag":422,"props":1573,"children":1576},{"href":1574,"rel":1575},"https://github.com/microsoft/winget-create",[426],[1577],{"type":419,"value":1578},"Windows Package Manager Manifest Creator",{"type":419,"value":1580}," 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":414,"tag":422,"props":1582,"children":1585},{"href":1583,"rel":1584},"https://aka.ms/wingetcreate/latest",[426],[1586],{"type":419,"value":1587},"this link",{"type":419,"value":1589}," but of course, it is available from winget: ",{"type":414,"tag":479,"props":1591,"children":1593},{"className":1592},[],[1594],{"type":419,"value":1595},"winget install wingetcreate",{"type":419,"value":457},{"type":414,"tag":415,"props":1598,"children":1599},{},[1600,1602,1609,1611,1618,1619,1626],{"type":419,"value":1601},"The main commands are ",{"type":414,"tag":422,"props":1603,"children":1606},{"href":1604,"rel":1605},"https://github.com/microsoft/winget-create/blob/main/doc/new.md",[426],[1607],{"type":419,"value":1608},"New",{"type":419,"value":1610},", ",{"type":414,"tag":422,"props":1612,"children":1615},{"href":1613,"rel":1614},"https://github.com/microsoft/winget-create/blob/main/doc/update.md",[426],[1616],{"type":419,"value":1617},"Update",{"type":419,"value":448},{"type":414,"tag":422,"props":1620,"children":1623},{"href":1621,"rel":1622},"https://github.com/microsoft/winget-create/blob/main/doc/submit.md",[426],[1624],{"type":419,"value":1625},"Submit",{"type":419,"value":457},{"type":414,"tag":1563,"props":1628,"children":1630},{"id":1629},"the-new-command",[1631,1632,1637],{"type":419,"value":1058},{"type":414,"tag":479,"props":1633,"children":1635},{"className":1634},[],[1636],{"type":419,"value":1608},{"type":419,"value":1638}," command",{"type":414,"tag":415,"props":1640,"children":1641},{},[1642,1644,1650],{"type":419,"value":1643},"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":414,"tag":422,"props":1645,"children":1647},{"href":1523,"rel":1646},[426],[1648],{"type":419,"value":1649},"winget community repository",{"type":419,"value":1651}," can be sometimes more efficient.",{"type":414,"tag":1563,"props":1653,"children":1655},{"id":1654},"the-update-command",[1656,1657,1662],{"type":419,"value":1058},{"type":414,"tag":479,"props":1658,"children":1660},{"className":1659},[],[1661],{"type":419,"value":1617},{"type":419,"value":1638},{"type":414,"tag":415,"props":1664,"children":1665},{},[1666,1668,1674],{"type":419,"value":1667},"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":414,"tag":479,"props":1669,"children":1671},{"className":1670},[],[1672],{"type":419,"value":1673},"submit",{"type":419,"value":1675}," 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":414,"tag":1563,"props":1677,"children":1679},{"id":1678},"the-submit-command",[1680,1681,1686],{"type":419,"value":1058},{"type":414,"tag":479,"props":1682,"children":1684},{"className":1683},[],[1685],{"type":419,"value":1625},{"type":419,"value":1638},{"type":414,"tag":415,"props":1688,"children":1689},{},[1690],{"type":419,"value":1691},"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":414,"tag":1563,"props":1693,"children":1695},{"id":1694},"what-else",[1696],{"type":419,"value":1697},"What else?",{"type":414,"tag":415,"props":1699,"children":1700},{},[1701,1703,1710],{"type":419,"value":1702},"If you look at the ",{"type":414,"tag":422,"props":1704,"children":1707},{"href":1705,"rel":1706},"https://github.com/microsoft/winget-create/blob/main/doc/settings.md",[426],[1708],{"type":419,"value":1709},"settings command",{"type":419,"value":1711}," 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":414,"tag":415,"props":1713,"children":1714},{},[1715],{"type":419,"value":1716},"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":414,"tag":459,"props":1718,"children":1720},{"id":1719},"automating-your-app-manifest-upgrade-with-github-actions",[1721],{"type":419,"value":1722},"Automating your app manifest upgrade with GitHub Actions",{"type":414,"tag":1563,"props":1724,"children":1726},{"id":1725},"why-using-github-actions-to-demonstrate-the-automation-of-app-manifests-upgrades",[1727],{"type":419,"value":1728},"Why using GitHub Actions to demonstrate the automation of app manifests upgrades?",{"type":414,"tag":415,"props":1730,"children":1731},{},[1732],{"type":414,"tag":609,"props":1733,"children":1737},{"alt":1734,"className":1735,"src":1736},"GitHub Actions documentation.",[613,614],"/posts/images/wingetcreate_githubactions.png",[],{"type":414,"tag":415,"props":1739,"children":1740},{},[1741],{"type":419,"value":1742},"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":414,"tag":415,"props":1744,"children":1745},{},[1746],{"type":419,"value":1747},"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":414,"tag":1563,"props":1749,"children":1751},{"id":1750},"an-interesting-use-case-for-with-nushell",[1752],{"type":419,"value":1753},"An interesting use case for with Nushell",{"type":414,"tag":415,"props":1755,"children":1756},{},[1757,1762],{"type":414,"tag":422,"props":1758,"children":1760},{"href":451,"rel":1759},[426],[1761],{"type":419,"value":455},{"type":419,"value":1763}," 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":414,"tag":415,"props":1765,"children":1766},{},[1767,1769,1776],{"type":419,"value":1768},"There was a ",{"type":414,"tag":422,"props":1770,"children":1773},{"href":1771,"rel":1772},"https://github.com/nushell/nushell/issues/1859",[426],[1774],{"type":419,"value":1775},"GitHub issue",{"type":419,"value":1777}," 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":414,"tag":415,"props":1779,"children":1780},{},[1781],{"type":414,"tag":609,"props":1782,"children":1786},{"alt":1783,"className":1784,"src":1785},"Nushell documentation page.",[613,614],"/posts/images/wingetcreate_nushell.png",[],{"type":414,"tag":415,"props":1788,"children":1789},{},[1790,1792,1799],{"type":419,"value":1791},"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":414,"tag":422,"props":1793,"children":1796},{"href":1794,"rel":1795},"https://docs.github.com/en/github/administering-a-repository/releasing-projects-on-github/about-releases",[426],[1797],{"type":419,"value":1798},"official documentation",{"type":419,"value":1800}," 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":414,"tag":415,"props":1802,"children":1803},{},[1804],{"type":414,"tag":609,"props":1805,"children":1809},{"alt":1806,"className":1807,"src":1808},"Nushell release page in GitHub.",[613,614],"/posts/images/wingetcreate_release.png",[],{"type":414,"tag":415,"props":1811,"children":1812},{},[1813,1815,1821],{"type":419,"value":1814},"Therefore, the idea was to update Nushell manifest with the latest version of Nushell using ",{"type":414,"tag":479,"props":1816,"children":1818},{"className":1817},[],[1819],{"type":419,"value":1820},"WingetCreate",{"type":419,"value":1822}," each time a new release of Nushell is published.",{"type":414,"tag":1563,"props":1824,"children":1826},{"id":1825},"triggering-a-new-workflow-from-a-release-event",[1827],{"type":419,"value":1828},"Triggering a new workflow from a release event",{"type":414,"tag":415,"props":1830,"children":1831},{},[1832,1834,1840,1842,1847],{"type":419,"value":1833},"Automating the app manifest upgrade of Nushell just meant creating a ",{"type":414,"tag":479,"props":1835,"children":1837},{"className":1836},[],[1838],{"type":419,"value":1839},"job",{"type":419,"value":1841}," in a GitHub Actions workflow that would call ",{"type":414,"tag":479,"props":1843,"children":1845},{"className":1844},[],[1846],{"type":419,"value":1820},{"type":419,"value":1848}," with the new version number and the new installer URL.",{"type":414,"tag":415,"props":1850,"children":1851},{},[1852,1854,1859,1861,1866],{"type":419,"value":1853},"I first wanted to modify the existing Nushell GitHub Actions workflow that was creating the releases by adding a new ",{"type":414,"tag":479,"props":1855,"children":1857},{"className":1856},[],[1858],{"type":419,"value":1839},{"type":419,"value":1860}," 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":414,"tag":479,"props":1862,"children":1864},{"className":1863},[],[1865],{"type":419,"value":1820},{"type":419,"value":457},{"type":414,"tag":415,"props":1868,"children":1869},{},[1870],{"type":419,"value":1871},"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":414,"tag":503,"props":1873,"children":1877},{"className":1874,"code":1875,"language":1876,"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",[1878],{"type":414,"tag":479,"props":1879,"children":1880},{"__ignoreMap":401},[1881,1904,1913,1926,1938,1965,1972,1984,1991,2003],{"type":414,"tag":512,"props":1882,"children":1883},{"class":514,"line":515},[1884,1890,1894,1899],{"type":414,"tag":512,"props":1885,"children":1887},{"style":1886},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[1888],{"type":419,"value":1889},"name",{"type":414,"tag":512,"props":1891,"children":1892},{"style":745},[1893],{"type":419,"value":730},{"type":414,"tag":512,"props":1895,"children":1896},{"style":812},[1897],{"type":419,"value":1898}," Submit Nushell package to Windows Package Manager Community Repository",{"type":414,"tag":512,"props":1900,"children":1901},{"style":535},[1902],{"type":419,"value":1903}," \n",{"type":414,"tag":512,"props":1905,"children":1906},{"class":514,"line":525},[1907],{"type":414,"tag":512,"props":1908,"children":1910},{"emptyLinePlaceholder":1909},true,[1911],{"type":419,"value":1912},"\n",{"type":414,"tag":512,"props":1914,"children":1915},{"class":514,"line":775},[1916,1922],{"type":414,"tag":512,"props":1917,"children":1919},{"style":1918},"--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC",[1920],{"type":419,"value":1921},"on",{"type":414,"tag":512,"props":1923,"children":1924},{"style":745},[1925],{"type":419,"value":772},{"type":414,"tag":512,"props":1927,"children":1928},{"class":514,"line":784},[1929,1934],{"type":414,"tag":512,"props":1930,"children":1931},{"style":1886},[1932],{"type":419,"value":1933},"  release",{"type":414,"tag":512,"props":1935,"children":1936},{"style":745},[1937],{"type":419,"value":772},{"type":414,"tag":512,"props":1939,"children":1940},{"class":514,"line":827},[1941,1946,1950,1955,1960],{"type":414,"tag":512,"props":1942,"children":1943},{"style":1886},[1944],{"type":419,"value":1945},"    types",{"type":414,"tag":512,"props":1947,"children":1948},{"style":745},[1949],{"type":419,"value":730},{"type":414,"tag":512,"props":1951,"children":1952},{"style":745},[1953],{"type":419,"value":1954}," [",{"type":414,"tag":512,"props":1956,"children":1957},{"style":812},[1958],{"type":419,"value":1959},"published",{"type":414,"tag":512,"props":1961,"children":1962},{"style":745},[1963],{"type":419,"value":1964},"]\n",{"type":414,"tag":512,"props":1966,"children":1967},{"class":514,"line":865},[1968],{"type":414,"tag":512,"props":1969,"children":1970},{"emptyLinePlaceholder":1909},[1971],{"type":419,"value":1912},{"type":414,"tag":512,"props":1973,"children":1974},{"class":514,"line":900},[1975,1980],{"type":414,"tag":512,"props":1976,"children":1977},{"style":1886},[1978],{"type":419,"value":1979},"jobs",{"type":414,"tag":512,"props":1981,"children":1982},{"style":745},[1983],{"type":419,"value":772},{"type":414,"tag":512,"props":1985,"children":1986},{"class":514,"line":909},[1987],{"type":414,"tag":512,"props":1988,"children":1989},{"emptyLinePlaceholder":1909},[1990],{"type":419,"value":1912},{"type":414,"tag":512,"props":1992,"children":1993},{"class":514,"line":1431},[1994,1999],{"type":414,"tag":512,"props":1995,"children":1996},{"style":1886},[1997],{"type":419,"value":1998},"  winget",{"type":414,"tag":512,"props":2000,"children":2001},{"style":745},[2002],{"type":419,"value":772},{"type":414,"tag":512,"props":2004,"children":2006},{"class":514,"line":2005},10,[2007,2012,2016],{"type":414,"tag":512,"props":2008,"children":2009},{"style":1886},[2010],{"type":419,"value":2011},"    name",{"type":414,"tag":512,"props":2013,"children":2014},{"style":745},[2015],{"type":419,"value":730},{"type":414,"tag":512,"props":2017,"children":2018},{"style":812},[2019],{"type":419,"value":2020}," Publish winget package\n",{"type":414,"tag":415,"props":2022,"children":2023},{},[2024],{"type":419,"value":2025},"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":414,"tag":1563,"props":2027,"children":2029},{"id":2028},"calling-wingetcreate-from-a-github-actions-workflow",[2030,2032,2037],{"type":419,"value":2031},"Calling ",{"type":414,"tag":479,"props":2033,"children":2035},{"className":2034},[],[2036],{"type":419,"value":1820},{"type":419,"value":2038}," from a GitHub Actions workflow.",{"type":414,"tag":415,"props":2040,"children":2041},{},[2042],{"type":419,"value":2043},"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":414,"tag":503,"props":2045,"children":2047},{"className":1874,"code":2046,"language":1876,"meta":401,"style":401},"jobs:\n\n  winget:\n    name: Publish winget package\n    runs-on: windows-latest\n",[2048],{"type":414,"tag":479,"props":2049,"children":2050},{"__ignoreMap":401},[2051,2062,2069,2080,2095],{"type":414,"tag":512,"props":2052,"children":2053},{"class":514,"line":515},[2054,2058],{"type":414,"tag":512,"props":2055,"children":2056},{"style":1886},[2057],{"type":419,"value":1979},{"type":414,"tag":512,"props":2059,"children":2060},{"style":745},[2061],{"type":419,"value":772},{"type":414,"tag":512,"props":2063,"children":2064},{"class":514,"line":525},[2065],{"type":414,"tag":512,"props":2066,"children":2067},{"emptyLinePlaceholder":1909},[2068],{"type":419,"value":1912},{"type":414,"tag":512,"props":2070,"children":2071},{"class":514,"line":775},[2072,2076],{"type":414,"tag":512,"props":2073,"children":2074},{"style":1886},[2075],{"type":419,"value":1998},{"type":414,"tag":512,"props":2077,"children":2078},{"style":745},[2079],{"type":419,"value":772},{"type":414,"tag":512,"props":2081,"children":2082},{"class":514,"line":784},[2083,2087,2091],{"type":414,"tag":512,"props":2084,"children":2085},{"style":1886},[2086],{"type":419,"value":2011},{"type":414,"tag":512,"props":2088,"children":2089},{"style":745},[2090],{"type":419,"value":730},{"type":414,"tag":512,"props":2092,"children":2093},{"style":812},[2094],{"type":419,"value":2020},{"type":414,"tag":512,"props":2096,"children":2097},{"class":514,"line":827},[2098,2103,2107],{"type":414,"tag":512,"props":2099,"children":2100},{"style":1886},[2101],{"type":419,"value":2102},"    runs-on",{"type":414,"tag":512,"props":2104,"children":2105},{"style":745},[2106],{"type":419,"value":730},{"type":414,"tag":512,"props":2108,"children":2109},{"style":812},[2110],{"type":419,"value":2111}," windows-latest\n",{"type":414,"tag":415,"props":2113,"children":2114},{},[2115,2117,2122,2124,2130],{"type":419,"value":2116},"This job will only contain one step that is the execution of the commands to call ",{"type":414,"tag":479,"props":2118,"children":2120},{"className":2119},[],[2121],{"type":419,"value":1820},{"type":419,"value":2123},". These commands will be in PowerShell as this is the default runner (",{"type":414,"tag":479,"props":2125,"children":2127},{"className":2126},[],[2128],{"type":419,"value":2129},"pwsh",{"type":419,"value":2131},") in a windows job.",{"type":414,"tag":503,"props":2133,"children":2135},{"className":1874,"code":2134,"language":1876,"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",[2136],{"type":414,"tag":479,"props":2137,"children":2138},{"__ignoreMap":401},[2139,2150,2165,2180,2192,2213],{"type":414,"tag":512,"props":2140,"children":2141},{"class":514,"line":515},[2142,2146],{"type":414,"tag":512,"props":2143,"children":2144},{"style":1886},[2145],{"type":419,"value":1998},{"type":414,"tag":512,"props":2147,"children":2148},{"style":745},[2149],{"type":419,"value":772},{"type":414,"tag":512,"props":2151,"children":2152},{"class":514,"line":525},[2153,2157,2161],{"type":414,"tag":512,"props":2154,"children":2155},{"style":1886},[2156],{"type":419,"value":2011},{"type":414,"tag":512,"props":2158,"children":2159},{"style":745},[2160],{"type":419,"value":730},{"type":414,"tag":512,"props":2162,"children":2163},{"style":812},[2164],{"type":419,"value":2020},{"type":414,"tag":512,"props":2166,"children":2167},{"class":514,"line":775},[2168,2172,2176],{"type":414,"tag":512,"props":2169,"children":2170},{"style":1886},[2171],{"type":419,"value":2102},{"type":414,"tag":512,"props":2173,"children":2174},{"style":745},[2175],{"type":419,"value":730},{"type":414,"tag":512,"props":2177,"children":2178},{"style":812},[2179],{"type":419,"value":2111},{"type":414,"tag":512,"props":2181,"children":2182},{"class":514,"line":784},[2183,2188],{"type":414,"tag":512,"props":2184,"children":2185},{"style":1886},[2186],{"type":419,"value":2187},"    steps",{"type":414,"tag":512,"props":2189,"children":2190},{"style":745},[2191],{"type":419,"value":772},{"type":414,"tag":512,"props":2193,"children":2194},{"class":514,"line":827},[2195,2200,2204,2208],{"type":414,"tag":512,"props":2196,"children":2197},{"style":745},[2198],{"type":419,"value":2199},"      -",{"type":414,"tag":512,"props":2201,"children":2202},{"style":1886},[2203],{"type":419,"value":1355},{"type":414,"tag":512,"props":2205,"children":2206},{"style":745},[2207],{"type":419,"value":730},{"type":414,"tag":512,"props":2209,"children":2210},{"style":812},[2211],{"type":419,"value":2212}," Submit package to Windows Package Manager Community Repository\n",{"type":414,"tag":512,"props":2214,"children":2215},{"class":514,"line":865},[2216,2221,2225],{"type":414,"tag":512,"props":2217,"children":2218},{"style":1886},[2219],{"type":419,"value":2220},"        run",{"type":414,"tag":512,"props":2222,"children":2223},{"style":745},[2224],{"type":419,"value":730},{"type":414,"tag":512,"props":2226,"children":2227},{"style":529},[2228],{"type":419,"value":1229},{"type":414,"tag":415,"props":2230,"children":2231},{},[2232,2234,2239],{"type":419,"value":2233},"First, we need to download the latest version of ",{"type":414,"tag":479,"props":2235,"children":2237},{"className":2236},[],[2238],{"type":419,"value":1820},{"type":419,"value":2240}," by using the following command :",{"type":414,"tag":503,"props":2242,"children":2245},{"className":2243,"code":2244,"language":248,"meta":401,"style":401},"language-powershell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe\n",[2246],{"type":414,"tag":479,"props":2247,"children":2248},{"__ignoreMap":401},[2249],{"type":414,"tag":512,"props":2250,"children":2251},{"class":514,"line":515},[2252,2257,2262,2267,2271,2275,2279,2284,2289,2294],{"type":414,"tag":512,"props":2253,"children":2254},{"style":535},[2255],{"type":419,"value":2256},"iwr https:",{"type":414,"tag":512,"props":2258,"children":2259},{"style":745},[2260],{"type":419,"value":2261},"//",{"type":414,"tag":512,"props":2263,"children":2264},{"style":535},[2265],{"type":419,"value":2266},"aka.ms",{"type":414,"tag":512,"props":2268,"children":2269},{"style":745},[2270],{"type":419,"value":548},{"type":414,"tag":512,"props":2272,"children":2273},{"style":535},[2274],{"type":419,"value":304},{"type":414,"tag":512,"props":2276,"children":2277},{"style":745},[2278],{"type":419,"value":548},{"type":414,"tag":512,"props":2280,"children":2281},{"style":535},[2282],{"type":419,"value":2283},"latest ",{"type":414,"tag":512,"props":2285,"children":2286},{"style":745},[2287],{"type":419,"value":2288},"-",{"type":414,"tag":512,"props":2290,"children":2291},{"style":535},[2292],{"type":419,"value":2293},"OutFile ",{"type":414,"tag":512,"props":2295,"children":2297},{"style":2296},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[2298],{"type":419,"value":2299},"wingetcreate.exe\n",{"type":414,"tag":415,"props":2301,"children":2302},{},[2303],{"type":419,"value":2304},"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":414,"tag":503,"props":2306,"children":2308},{"className":2243,"code":2307,"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",[2309],{"type":414,"tag":479,"props":2310,"children":2311},{"__ignoreMap":401},[2312,2359],{"type":414,"tag":512,"props":2313,"children":2314},{"class":514,"line":515},[2315,2320,2325,2330,2335,2340,2345,2350,2354],{"type":414,"tag":512,"props":2316,"children":2317},{"style":745},[2318],{"type":419,"value":2319},"$",{"type":414,"tag":512,"props":2321,"children":2322},{"style":535},[2323],{"type":419,"value":2324},"github ",{"type":414,"tag":512,"props":2326,"children":2327},{"style":745},[2328],{"type":419,"value":2329},"=",{"type":414,"tag":512,"props":2331,"children":2332},{"style":2296},[2333],{"type":419,"value":2334}," Get-Content",{"type":414,"tag":512,"props":2336,"children":2337},{"style":745},[2338],{"type":419,"value":2339}," '",{"type":414,"tag":512,"props":2341,"children":2342},{"style":812},[2343],{"type":419,"value":2344},"${{ github.event_path }}",{"type":414,"tag":512,"props":2346,"children":2347},{"style":745},[2348],{"type":419,"value":2349},"'",{"type":414,"tag":512,"props":2351,"children":2352},{"style":745},[2353],{"type":419,"value":1319},{"type":414,"tag":512,"props":2355,"children":2356},{"style":2296},[2357],{"type":419,"value":2358}," ConvertFrom-Json\n",{"type":414,"tag":512,"props":2360,"children":2361},{"class":514,"line":525},[2362,2366,2371,2375,2380,2385,2389,2394,2398,2403,2408,2412,2417,2421,2425,2430,2434,2439,2443,2448],{"type":414,"tag":512,"props":2363,"children":2364},{"style":745},[2365],{"type":419,"value":2319},{"type":414,"tag":512,"props":2367,"children":2368},{"style":535},[2369],{"type":419,"value":2370},"installerUrl ",{"type":414,"tag":512,"props":2372,"children":2373},{"style":745},[2374],{"type":419,"value":2329},{"type":414,"tag":512,"props":2376,"children":2377},{"style":745},[2378],{"type":419,"value":2379}," $",{"type":414,"tag":512,"props":2381,"children":2382},{"style":535},[2383],{"type":419,"value":2384},"github.release.assets ",{"type":414,"tag":512,"props":2386,"children":2387},{"style":745},[2388],{"type":419,"value":1264},{"type":414,"tag":512,"props":2390,"children":2391},{"style":2296},[2392],{"type":419,"value":2393}," Where-Object",{"type":414,"tag":512,"props":2395,"children":2396},{"style":745},[2397],{"type":419,"value":1376},{"type":414,"tag":512,"props":2399,"children":2400},{"style":535},[2401],{"type":419,"value":2402},"Property name ",{"type":414,"tag":512,"props":2404,"children":2405},{"style":745},[2406],{"type":419,"value":2407},"-match",{"type":414,"tag":512,"props":2409,"children":2410},{"style":745},[2411],{"type":419,"value":2339},{"type":414,"tag":512,"props":2413,"children":2414},{"style":812},[2415],{"type":419,"value":2416},"windows.msi",{"type":414,"tag":512,"props":2418,"children":2419},{"style":745},[2420],{"type":419,"value":2349},{"type":414,"tag":512,"props":2422,"children":2423},{"style":745},[2424],{"type":419,"value":1319},{"type":414,"tag":512,"props":2426,"children":2427},{"style":535},[2428],{"type":419,"value":2429}," Select ",{"type":414,"tag":512,"props":2431,"children":2432},{"style":745},[2433],{"type":419,"value":2288},{"type":414,"tag":512,"props":2435,"children":2436},{"style":535},[2437],{"type":419,"value":2438},"ExpandProperty browser_download_url ",{"type":414,"tag":512,"props":2440,"children":2441},{"style":745},[2442],{"type":419,"value":2288},{"type":414,"tag":512,"props":2444,"children":2445},{"style":535},[2446],{"type":419,"value":2447},"First ",{"type":414,"tag":512,"props":2449,"children":2450},{"style":541},[2451],{"type":419,"value":2452},"1\n",{"type":414,"tag":2454,"props":2455,"children":2456},"blockquote",{},[2457],{"type":414,"tag":415,"props":2458,"children":2459},{},[2460],{"type":419,"value":2461},"💡 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":414,"tag":415,"props":2463,"children":2464},{},[2465,2467,2472,2474,2479],{"type":419,"value":2466},"Third, we can call the ",{"type":414,"tag":479,"props":2468,"children":2470},{"className":2469},[],[2471],{"type":419,"value":1820},{"type":419,"value":2473}," update command by specifying the version, the URL of the installer, and a Personal Access Token that will be used by ",{"type":414,"tag":479,"props":2475,"children":2477},{"className":2476},[],[2478],{"type":419,"value":1820},{"type":419,"value":2480}," 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":414,"tag":415,"props":2482,"children":2483},{},[2484],{"type":414,"tag":609,"props":2485,"children":2489},{"alt":2486,"className":2487,"src":2488},"Screen to define Personal Access Tokens.",[613,614],"/posts/images/wingetcreate_pat.png",[],{"type":414,"tag":415,"props":2491,"children":2492},{},[2493],{"type":419,"value":2494},"Here you can see a run of the workflow in GitHub:",{"type":414,"tag":415,"props":2496,"children":2497},{},[2498],{"type":414,"tag":609,"props":2499,"children":2503},{"alt":2500,"className":2501,"src":2502},"GitHub Actions workflow running.",[613,614],"/posts/images/wingetcreate_wokflow_1.png",[],{"type":414,"tag":459,"props":2505,"children":2507},{"id":2506},"overview-of-the-created-workflow",[2508],{"type":419,"value":2509},"Overview of the created workflow",{"type":414,"tag":415,"props":2511,"children":2512},{},[2513,2515,2522],{"type":419,"value":2514},"You can find the complete workflow below and ",{"type":414,"tag":422,"props":2516,"children":2519},{"href":2517,"rel":2518},"https://github.com/nushell/nushell/blob/main/.github/workflows/winget-submission.yml",[426],[2520],{"type":419,"value":2521},"here",{"type":419,"value":2523}," in the Nushell repository.",{"type":414,"tag":503,"props":2525,"children":2527},{"className":1874,"code":2526,"language":1876,"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",[2528],{"type":414,"tag":479,"props":2529,"children":2530},{"__ignoreMap":401},[2531,2550,2557,2568,2579,2602,2609,2620,2627,2638,2653,2669,2681,2701,2717,2726,2735,2744],{"type":414,"tag":512,"props":2532,"children":2533},{"class":514,"line":515},[2534,2538,2542,2546],{"type":414,"tag":512,"props":2535,"children":2536},{"style":1886},[2537],{"type":419,"value":1889},{"type":414,"tag":512,"props":2539,"children":2540},{"style":745},[2541],{"type":419,"value":730},{"type":414,"tag":512,"props":2543,"children":2544},{"style":812},[2545],{"type":419,"value":1898},{"type":414,"tag":512,"props":2547,"children":2548},{"style":535},[2549],{"type":419,"value":1903},{"type":414,"tag":512,"props":2551,"children":2552},{"class":514,"line":525},[2553],{"type":414,"tag":512,"props":2554,"children":2555},{"emptyLinePlaceholder":1909},[2556],{"type":419,"value":1912},{"type":414,"tag":512,"props":2558,"children":2559},{"class":514,"line":775},[2560,2564],{"type":414,"tag":512,"props":2561,"children":2562},{"style":1918},[2563],{"type":419,"value":1921},{"type":414,"tag":512,"props":2565,"children":2566},{"style":745},[2567],{"type":419,"value":772},{"type":414,"tag":512,"props":2569,"children":2570},{"class":514,"line":784},[2571,2575],{"type":414,"tag":512,"props":2572,"children":2573},{"style":1886},[2574],{"type":419,"value":1933},{"type":414,"tag":512,"props":2576,"children":2577},{"style":745},[2578],{"type":419,"value":772},{"type":414,"tag":512,"props":2580,"children":2581},{"class":514,"line":827},[2582,2586,2590,2594,2598],{"type":414,"tag":512,"props":2583,"children":2584},{"style":1886},[2585],{"type":419,"value":1945},{"type":414,"tag":512,"props":2587,"children":2588},{"style":745},[2589],{"type":419,"value":730},{"type":414,"tag":512,"props":2591,"children":2592},{"style":745},[2593],{"type":419,"value":1954},{"type":414,"tag":512,"props":2595,"children":2596},{"style":812},[2597],{"type":419,"value":1959},{"type":414,"tag":512,"props":2599,"children":2600},{"style":745},[2601],{"type":419,"value":1964},{"type":414,"tag":512,"props":2603,"children":2604},{"class":514,"line":865},[2605],{"type":414,"tag":512,"props":2606,"children":2607},{"emptyLinePlaceholder":1909},[2608],{"type":419,"value":1912},{"type":414,"tag":512,"props":2610,"children":2611},{"class":514,"line":900},[2612,2616],{"type":414,"tag":512,"props":2613,"children":2614},{"style":1886},[2615],{"type":419,"value":1979},{"type":414,"tag":512,"props":2617,"children":2618},{"style":745},[2619],{"type":419,"value":772},{"type":414,"tag":512,"props":2621,"children":2622},{"class":514,"line":909},[2623],{"type":414,"tag":512,"props":2624,"children":2625},{"emptyLinePlaceholder":1909},[2626],{"type":419,"value":1912},{"type":414,"tag":512,"props":2628,"children":2629},{"class":514,"line":1431},[2630,2634],{"type":414,"tag":512,"props":2631,"children":2632},{"style":1886},[2633],{"type":419,"value":1998},{"type":414,"tag":512,"props":2635,"children":2636},{"style":745},[2637],{"type":419,"value":772},{"type":414,"tag":512,"props":2639,"children":2640},{"class":514,"line":2005},[2641,2645,2649],{"type":414,"tag":512,"props":2642,"children":2643},{"style":1886},[2644],{"type":419,"value":2011},{"type":414,"tag":512,"props":2646,"children":2647},{"style":745},[2648],{"type":419,"value":730},{"type":414,"tag":512,"props":2650,"children":2651},{"style":812},[2652],{"type":419,"value":2020},{"type":414,"tag":512,"props":2654,"children":2656},{"class":514,"line":2655},11,[2657,2661,2665],{"type":414,"tag":512,"props":2658,"children":2659},{"style":1886},[2660],{"type":419,"value":2102},{"type":414,"tag":512,"props":2662,"children":2663},{"style":745},[2664],{"type":419,"value":730},{"type":414,"tag":512,"props":2666,"children":2667},{"style":812},[2668],{"type":419,"value":2111},{"type":414,"tag":512,"props":2670,"children":2672},{"class":514,"line":2671},12,[2673,2677],{"type":414,"tag":512,"props":2674,"children":2675},{"style":1886},[2676],{"type":419,"value":2187},{"type":414,"tag":512,"props":2678,"children":2679},{"style":745},[2680],{"type":419,"value":772},{"type":414,"tag":512,"props":2682,"children":2684},{"class":514,"line":2683},13,[2685,2689,2693,2697],{"type":414,"tag":512,"props":2686,"children":2687},{"style":745},[2688],{"type":419,"value":2199},{"type":414,"tag":512,"props":2690,"children":2691},{"style":1886},[2692],{"type":419,"value":1355},{"type":414,"tag":512,"props":2694,"children":2695},{"style":745},[2696],{"type":419,"value":730},{"type":414,"tag":512,"props":2698,"children":2699},{"style":812},[2700],{"type":419,"value":2212},{"type":414,"tag":512,"props":2702,"children":2704},{"class":514,"line":2703},14,[2705,2709,2713],{"type":414,"tag":512,"props":2706,"children":2707},{"style":1886},[2708],{"type":419,"value":2220},{"type":414,"tag":512,"props":2710,"children":2711},{"style":745},[2712],{"type":419,"value":730},{"type":414,"tag":512,"props":2714,"children":2715},{"style":529},[2716],{"type":419,"value":1229},{"type":414,"tag":512,"props":2718,"children":2720},{"class":514,"line":2719},15,[2721],{"type":414,"tag":512,"props":2722,"children":2723},{"style":812},[2724],{"type":419,"value":2725},"          iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe\n",{"type":414,"tag":512,"props":2727,"children":2729},{"class":514,"line":2728},16,[2730],{"type":414,"tag":512,"props":2731,"children":2732},{"style":812},[2733],{"type":419,"value":2734},"          $github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json\n",{"type":414,"tag":512,"props":2736,"children":2738},{"class":514,"line":2737},17,[2739],{"type":414,"tag":512,"props":2740,"children":2741},{"style":812},[2742],{"type":419,"value":2743},"          $installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1\n",{"type":414,"tag":512,"props":2745,"children":2747},{"class":514,"line":2746},18,[2748],{"type":414,"tag":512,"props":2749,"children":2750},{"style":812},[2751],{"type":419,"value":2752},"          .\\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}\n",{"type":414,"tag":415,"props":2754,"children":2755},{},[2756],{"type":419,"value":2757},"Here is what a Pull Request generated by the GitHub Actions workflow looks like:",{"type":414,"tag":415,"props":2759,"children":2760},{},[2761],{"type":414,"tag":609,"props":2762,"children":2766},{"alt":2763,"className":2764,"src":2765},"Pull Request in the winget-pkgs repository.",[613,614],"/posts/images/wingetcreate_pr.png",[],{"type":414,"tag":459,"props":2768,"children":2770},{"id":2769},"to-summarize",[2771],{"type":419,"value":2772},"To summarize",{"type":414,"tag":415,"props":2774,"children":2775},{},[2776],{"type":419,"value":2777},"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":414,"tag":415,"props":2779,"children":2780},{},[2781],{"type":419,"value":2782},"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":414,"tag":415,"props":2784,"children":2785},{},[2786,2788,2795,2797,2804,2805,2812],{"type":419,"value":2787},"A big thank you to ",{"type":414,"tag":422,"props":2789,"children":2792},{"href":2790,"rel":2791},"https://twitter.com/ethomson",[426],[2793],{"type":419,"value":2794},"Edward Thomson",{"type":419,"value":2796}," who explained to me how to retrieve GitHub Actions contexts in PowerShell. Thanks also to ",{"type":414,"tag":422,"props":2798,"children":2801},{"href":2799,"rel":2800},"https://twitter.com/fdncred",[426],[2802],{"type":419,"value":2803},"Darren Schroeder",{"type":419,"value":448},{"type":414,"tag":422,"props":2806,"children":2809},{"href":2807,"rel":2808},"https://twitter.com/jntrnr",[426],[2810],{"type":419,"value":2811},"Jonathan Turner",{"type":419,"value":2813}," who supported me to set up a workflow that publishes new releases of Nushell in winget.",{"type":414,"tag":1469,"props":2815,"children":2816},{},[2817],{"type":419,"value":1473},{"title":401,"searchDepth":525,"depth":525,"links":2819},[2820,2821,2831,2838,2839],{"id":1512,"depth":525,"text":1515},{"id":1558,"depth":525,"text":1561,"children":2822},[2823,2824,2826,2828,2830],{"id":1565,"depth":775,"text":1568},{"id":1629,"depth":775,"text":2825},"The New command",{"id":1654,"depth":775,"text":2827},"The Update command",{"id":1678,"depth":775,"text":2829},"The Submit command",{"id":1694,"depth":775,"text":1697},{"id":1719,"depth":525,"text":1722,"children":2832},[2833,2834,2835,2836],{"id":1725,"depth":775,"text":1728},{"id":1750,"depth":775,"text":1753},{"id":1825,"depth":775,"text":1828},{"id":2028,"depth":775,"text":2837},"Calling WingetCreate from a GitHub Actions workflow.",{"id":2506,"depth":525,"text":2509},{"id":2769,"depth":525,"text":2772},"content:1.posts:21.wingetcreate.md","1.posts/21.wingetcreate.md",{"_path":25,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":24,"description":2843,"lead":2844,"date":2845,"image":2846,"badge":2848,"tags":2850,"body":2851,"_type":1480,"_id":3428,"_source":1482,"_file":3429,"_extension":1484},"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":2847},"/images/branches_1.jpg",{"label":2849},"Tips",[206,241,230,243],{"type":411,"children":2852,"toc":3422},[2853,2857,2863,2868,2881,2914,2919,2932,2938,2957,2962,2972,2993,3003,3016,3047,3053,3073,3082,3103,3112,3123,3132,3137,3310,3316,3336,3370,3391,3404,3413,3418],{"type":414,"tag":415,"props":2854,"children":2855},{},[2856],{"type":419,"value":2843},{"type":414,"tag":459,"props":2858,"children":2860},{"id":2859},"why-do-i-end-up-having-outdated-local-branches-on-my-git-repositories",[2861],{"type":419,"value":2862},"Why do I end up having outdated local branches on my git repositories?",{"type":414,"tag":415,"props":2864,"children":2865},{},[2866],{"type":419,"value":2867},"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":414,"tag":415,"props":2869,"children":2870},{},[2871,2873,2879],{"type":419,"value":2872},"At work I am working in a small team of developers, we host our git repositories in ",{"type":414,"tag":422,"props":2874,"children":2877},{"href":2875,"rel":2876},"https://azure.microsoft.com/en-us/services/devops/repos/",[426],[2878],{"type":419,"value":343},{"type":419,"value":2880}," and we try to respect the following practices in our daily development:",{"type":414,"tag":2882,"props":2883,"children":2884},"ul",{},[2885,2891,2896],{"type":414,"tag":2886,"props":2887,"children":2888},"li",{},[2889],{"type":419,"value":2890},"having a main branch (master) on which nobody can commit directly",{"type":414,"tag":2886,"props":2892,"children":2893},{},[2894],{"type":419,"value":2895},"always create a short-lived branch (also called feature branch) when developing a new feature of the application",{"type":414,"tag":2886,"props":2897,"children":2898},{},[2899,2901],{"type":419,"value":2900},"only merge a feature branch on the main branch through an Azure DevOps pull request\n",{"type":414,"tag":2882,"props":2902,"children":2903},{},[2904,2909],{"type":414,"tag":2886,"props":2905,"children":2906},{},[2907],{"type":419,"value":2908},"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":414,"tag":2886,"props":2910,"children":2911},{},[2912],{"type":419,"value":2913},"the PR can only be completed after a code review of at least one member of the team",{"type":414,"tag":415,"props":2915,"children":2916},{},[2917],{"type":419,"value":2918},"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":414,"tag":415,"props":2920,"children":2921},{},[2922,2924,2930],{"type":419,"value":2923},"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":414,"tag":479,"props":2925,"children":2927},{"className":2926},[],[2928],{"type":419,"value":2929},"git fetch --prune",{"type":419,"value":2931}," 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":414,"tag":459,"props":2933,"children":2935},{"id":2934},"git-commands-to-identify-and-delete-outdated-branches",[2936],{"type":419,"value":2937},"Git commands to identify and delete outdated branches.",{"type":414,"tag":415,"props":2939,"children":2940},{},[2941,2943,2948,2950,2956],{"type":419,"value":2942},"As my outdated branches are already removed from my remote (thanks to ",{"type":414,"tag":479,"props":2944,"children":2946},{"className":2945},[],[2947],{"type":419,"value":2929},{"type":419,"value":2949},") 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":414,"tag":479,"props":2951,"children":2953},{"className":2952},[],[2954],{"type":419,"value":2955},"git branch --merged",{"type":419,"value":956},{"type":414,"tag":415,"props":2958,"children":2959},{},[2960],{"type":419,"value":2961},"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":414,"tag":415,"props":2963,"children":2964},{},[2965],{"type":414,"tag":609,"props":2966,"children":2971},{"alt":2967,"className":2968,"src":2969,"width":2970},"List all git branches in the terminal.",[613,614],"/posts/images/cleaningbranches_shell_1.png",800,[],{"type":414,"tag":415,"props":2973,"children":2974},{},[2975,2977,2983,2985,2991],{"type":419,"value":2976},"The command ",{"type":414,"tag":479,"props":2978,"children":2980},{"className":2979},[],[2981],{"type":419,"value":2982},"git branch -vl",{"type":419,"value":2984}," (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":414,"tag":479,"props":2986,"children":2988},{"className":2987},[],[2989],{"type":419,"value":2990},"[gone]",{"type":419,"value":2992}," for them. These branches correspond to the outdated branches we want to delete.",{"type":414,"tag":415,"props":2994,"children":2995},{},[2996],{"type":414,"tag":609,"props":2997,"children":3002},{"alt":2998,"className":2999,"src":3000,"width":3001},"List all git branches with verbose tag in terminal.",[613,614],"/posts/images/cleaningbranches_shell_2.png",1000,[],{"type":414,"tag":415,"props":3004,"children":3005},{},[3006,3008,3014],{"type":419,"value":3007},"We know how to identify the outdated branches but we need a command to delete them which is the ",{"type":414,"tag":479,"props":3009,"children":3011},{"className":3010},[],[3012],{"type":419,"value":3013},"git branch -D",{"type":419,"value":3015}," command. Now we only need a script to associate the output and input of these two commands to automate the deletion.",{"type":414,"tag":415,"props":3017,"children":3018},{},[3019,3021,3028,3030,3036,3038,3045],{"type":419,"value":3020},"You can find on Stackoverflow some posts like ",{"type":414,"tag":422,"props":3022,"children":3025},{"href":3023,"rel":3024},"https://stackoverflow.com/questions/7726949/remove-tracking-branches-no-longer-on-remote",[426],[3026],{"type":419,"value":3027},"this one",{"type":419,"value":3029}," 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":414,"tag":422,"props":3031,"children":3034},{"href":3032,"rel":3033},"https://github.com/nushell/nushell",[426],[3035],{"type":419,"value":243},{"type":419,"value":3037}," 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":414,"tag":422,"props":3039,"children":3042},{"href":3040,"rel":3041},"https://www.jonathanturner.org/2019/08/introducing-nushell.html",[426],[3043],{"type":419,"value":3044},"introduction post",{"type":419,"value":3046}," of Jonathan Turner.",{"type":414,"tag":459,"props":3048,"children":3050},{"id":3049},"lets-script-that-with-nushell",[3051],{"type":419,"value":3052},"Let's script that with nushell!",{"type":414,"tag":415,"props":3054,"children":3055},{},[3056,3058,3063,3065,3071],{"type":419,"value":3057},"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":414,"tag":479,"props":3059,"children":3061},{"className":3060},[],[3062],{"type":419,"value":2982},{"type":419,"value":3064}," output (we added an extra ",{"type":414,"tag":479,"props":3066,"children":3068},{"className":3067},[],[3069],{"type":419,"value":3070},"*/*",{"type":419,"value":3072}," argument as we are only interested in posts branches).",{"type":414,"tag":415,"props":3074,"children":3075},{},[3076],{"type":414,"tag":609,"props":3077,"children":3081},{"alt":3078,"className":3079,"src":3080,"width":3001},"List git branches in table in the terminal.",[613,614],"/posts/images/cleaningbranches_shell_3.png",[],{"type":414,"tag":415,"props":3083,"children":3084},{},[3085,3087,3093,3095,3101],{"type":419,"value":3086},"Then we can split the different lines into columns that we can name with the ",{"type":414,"tag":479,"props":3088,"children":3090},{"className":3089},[],[3091],{"type":419,"value":3092},"split column",{"type":419,"value":3094}," command. We use spaces to correctly split a line and the option ",{"type":414,"tag":479,"props":3096,"children":3098},{"className":3097},[],[3099],{"type":419,"value":3100},"--collapse-empty",{"type":419,"value":3102}," to remove the empty columns.",{"type":414,"tag":415,"props":3104,"children":3105},{},[3106],{"type":414,"tag":609,"props":3107,"children":3111},{"alt":3108,"className":3109,"src":3110,"width":3001},"List git branches in table with columns by property in the terminal.",[613,614],"/posts/images/cleaningbranches_shell_4.png",[],{"type":414,"tag":415,"props":3113,"children":3114},{},[3115,3117,3122],{"type":419,"value":3116},"We then just have to filter the table to get only the lines with the Status ",{"type":414,"tag":479,"props":3118,"children":3120},{"className":3119},[],[3121],{"type":419,"value":2990},{"type":419,"value":457},{"type":414,"tag":415,"props":3124,"children":3125},{},[3126],{"type":414,"tag":609,"props":3127,"children":3131},{"alt":3128,"className":3129,"src":3130,"width":3001},"Filter on git branches gone in the terminal.",[613,614],"/posts/images/cleaningbranches_shell_5.png",[],{"type":414,"tag":415,"props":3133,"children":3134},{},[3135],{"type":419,"value":3136},"And the final script:",{"type":414,"tag":503,"props":3138,"children":3142},{"className":3139,"code":3140,"language":3141,"meta":401,"style":401},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","git branch -vl '*/*' | lines | split column \" \" BranchName Hash Status --collapse-empty | where Status == '[gone]' | each { |it| git branch -D $it.BranchName }\n","bash",[3143],{"type":414,"tag":479,"props":3144,"children":3145},{"__ignoreMap":401},[3146],{"type":414,"tag":512,"props":3147,"children":3148},{"class":514,"line":515},[3149,3153,3158,3163,3167,3171,3175,3179,3184,3188,3193,3198,3202,3206,3211,3216,3221,3226,3230,3235,3239,3244,3248,3252,3256,3260,3265,3269,3273,3277,3281,3286,3290,3295,3300,3305],{"type":414,"tag":512,"props":3150,"children":3151},{"style":793},[3152],{"type":419,"value":241},{"type":414,"tag":512,"props":3154,"children":3155},{"style":812},[3156],{"type":419,"value":3157}," branch",{"type":414,"tag":512,"props":3159,"children":3160},{"style":812},[3161],{"type":419,"value":3162}," -vl",{"type":414,"tag":512,"props":3164,"children":3165},{"style":745},[3166],{"type":419,"value":2339},{"type":414,"tag":512,"props":3168,"children":3169},{"style":812},[3170],{"type":419,"value":3070},{"type":414,"tag":512,"props":3172,"children":3173},{"style":745},[3174],{"type":419,"value":2349},{"type":414,"tag":512,"props":3176,"children":3177},{"style":745},[3178],{"type":419,"value":1319},{"type":414,"tag":512,"props":3180,"children":3181},{"style":793},[3182],{"type":419,"value":3183}," lines",{"type":414,"tag":512,"props":3185,"children":3186},{"style":745},[3187],{"type":419,"value":1319},{"type":414,"tag":512,"props":3189,"children":3190},{"style":793},[3191],{"type":419,"value":3192}," split",{"type":414,"tag":512,"props":3194,"children":3195},{"style":812},[3196],{"type":419,"value":3197}," column",{"type":414,"tag":512,"props":3199,"children":3200},{"style":745},[3201],{"type":419,"value":809},{"type":414,"tag":512,"props":3203,"children":3204},{"style":745},[3205],{"type":419,"value":809},{"type":414,"tag":512,"props":3207,"children":3208},{"style":812},[3209],{"type":419,"value":3210}," BranchName",{"type":414,"tag":512,"props":3212,"children":3213},{"style":812},[3214],{"type":419,"value":3215}," Hash",{"type":414,"tag":512,"props":3217,"children":3218},{"style":812},[3219],{"type":419,"value":3220}," Status",{"type":414,"tag":512,"props":3222,"children":3223},{"style":812},[3224],{"type":419,"value":3225}," --collapse-empty",{"type":414,"tag":512,"props":3227,"children":3228},{"style":745},[3229],{"type":419,"value":1319},{"type":414,"tag":512,"props":3231,"children":3232},{"style":793},[3233],{"type":419,"value":3234}," where",{"type":414,"tag":512,"props":3236,"children":3237},{"style":812},[3238],{"type":419,"value":3220},{"type":414,"tag":512,"props":3240,"children":3241},{"style":812},[3242],{"type":419,"value":3243}," ==",{"type":414,"tag":512,"props":3245,"children":3246},{"style":745},[3247],{"type":419,"value":2339},{"type":414,"tag":512,"props":3249,"children":3250},{"style":812},[3251],{"type":419,"value":2990},{"type":414,"tag":512,"props":3253,"children":3254},{"style":745},[3255],{"type":419,"value":2349},{"type":414,"tag":512,"props":3257,"children":3258},{"style":745},[3259],{"type":419,"value":1319},{"type":414,"tag":512,"props":3261,"children":3262},{"style":793},[3263],{"type":419,"value":3264}," each",{"type":414,"tag":512,"props":3266,"children":3267},{"style":812},[3268],{"type":419,"value":1259},{"type":414,"tag":512,"props":3270,"children":3271},{"style":745},[3272],{"type":419,"value":1319},{"type":414,"tag":512,"props":3274,"children":3275},{"style":793},[3276],{"type":419,"value":268},{"type":414,"tag":512,"props":3278,"children":3279},{"style":745},[3280],{"type":419,"value":1264},{"type":414,"tag":512,"props":3282,"children":3283},{"style":793},[3284],{"type":419,"value":3285}," git",{"type":414,"tag":512,"props":3287,"children":3288},{"style":812},[3289],{"type":419,"value":3157},{"type":414,"tag":512,"props":3291,"children":3292},{"style":812},[3293],{"type":419,"value":3294}," -D",{"type":414,"tag":512,"props":3296,"children":3297},{"style":535},[3298],{"type":419,"value":3299}," $it",{"type":414,"tag":512,"props":3301,"children":3302},{"style":812},[3303],{"type":419,"value":3304},".BranchName",{"type":414,"tag":512,"props":3306,"children":3307},{"style":812},[3308],{"type":419,"value":3309}," }\n",{"type":414,"tag":459,"props":3311,"children":3313},{"id":3312},"make-it-a-git-alias",[3314],{"type":419,"value":3315},"Make it a git alias.",{"type":414,"tag":415,"props":3317,"children":3318},{},[3319,3321,3327,3329,3335],{"type":419,"value":3320},"We can integrate this script into our git commands by creating a git alias. Let's say I want to create the alias ",{"type":414,"tag":479,"props":3322,"children":3324},{"className":3323},[],[3325],{"type":419,"value":3326},"bcl",{"type":419,"value":3328}," for branch clean up, we only need to add the following to our ",{"type":414,"tag":479,"props":3330,"children":3332},{"className":3331},[],[3333],{"type":419,"value":3334},".gitconfig",{"type":419,"value":730},{"type":414,"tag":503,"props":3337,"children":3341},{"className":3338,"code":3339,"language":3340,"meta":401,"style":401},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[alias]\n    bcl = !nu \\\"D:\\\\gitalias_bcl.nu\\\"\n","yaml",[3342],{"type":414,"tag":479,"props":3343,"children":3344},{"__ignoreMap":401},[3345,3362],{"type":414,"tag":512,"props":3346,"children":3347},{"class":514,"line":515},[3348,3353,3358],{"type":414,"tag":512,"props":3349,"children":3350},{"style":745},[3351],{"type":419,"value":3352},"[",{"type":414,"tag":512,"props":3354,"children":3355},{"style":812},[3356],{"type":419,"value":3357},"alias",{"type":414,"tag":512,"props":3359,"children":3360},{"style":745},[3361],{"type":419,"value":1964},{"type":414,"tag":512,"props":3363,"children":3364},{"class":514,"line":525},[3365],{"type":414,"tag":512,"props":3366,"children":3367},{"style":812},[3368],{"type":419,"value":3369},"    bcl = !nu \\\"D:\\\\gitalias_bcl.nu\\\"\n",{"type":414,"tag":415,"props":3371,"children":3372},{},[3373,3375,3381,3383,3389],{"type":419,"value":3374},"where ",{"type":414,"tag":479,"props":3376,"children":3378},{"className":3377},[],[3379],{"type":419,"value":3380},"gitalias_bcl.nu",{"type":419,"value":3382}," is the nu script file we created earlier (it's located here in the ",{"type":414,"tag":479,"props":3384,"children":3386},{"className":3385},[],[3387],{"type":419,"value":3388},"D://",{"type":419,"value":3390}," drive but can be created anywhere).",{"type":414,"tag":415,"props":3392,"children":3393},{},[3394,3396,3402],{"type":419,"value":3395},"Now we can simply do a ",{"type":414,"tag":479,"props":3397,"children":3399},{"className":3398},[],[3400],{"type":419,"value":3401},"git bcl",{"type":419,"value":3403}," to clean our outdated local git branches.",{"type":414,"tag":415,"props":3405,"children":3406},{},[3407],{"type":414,"tag":609,"props":3408,"children":3412},{"alt":3409,"className":3410,"src":3411,"width":2970},"List oudated git branches  in the terminal.",[613,614],"/posts/images/cleaningbranches_shell_6.png",[],{"type":414,"tag":415,"props":3414,"children":3415},{},[3416],{"type":419,"value":3417},"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":414,"tag":1469,"props":3419,"children":3420},{},[3421],{"type":419,"value":1473},{"title":401,"searchDepth":525,"depth":525,"links":3423},[3424,3425,3426,3427],{"id":2859,"depth":525,"text":2862},{"id":2934,"depth":525,"text":2937},{"id":3049,"depth":525,"text":3052},{"id":3312,"depth":525,"text":3315},"content:1.posts:6.cleaning-git-branches.md","1.posts/6.cleaning-git-branches.md",1716749600683]